墨屿 InkIsle:我为什么又写了一个博客系统

从 Hexo 个人博客迁移出发,介绍墨屿 InkIsle 的背景、设计取舍、实现方案、迁移结果、使用方式和未来开源计划。

2017 年,我把个人博客从 WordPress 换成了 Hexo。当时的判断很朴素:WordPress 对一个个人博客来说太重了,静态 HTML 更便宜、更安全,也更容易部署。

几年之后,我又把 Hexo 换掉了。原因不是 Hexo 不好,而是我的需求变了。

我希望博客仍然是 Markdown 驱动,仍然能静态输出,但写作、构建、主题、多语言、搜索、RSS、JSON Feed、llms.txt 这些东西应该作为一个整体被设计。尤其是 AI 时代,公开内容不只是给人读,也会被搜索引擎、RSS 阅读器和 AI agent 消费。于是我写了一个新的博客系统:墨屿,英文名 InkIsle。

为什么想要一个新的博客系统

我对旧博客系统的不满主要有三个。

第一是性能和发布体验。Hexo 是成熟方案,但在我的博客内容逐渐变多、迁移历史文章和多语言内容之后,构建和本地调试体验不够理想。我希望从提交到线上可见尽量控制在分钟级,平时本地写作也不要有明显等待。

第二是主题和内容耦合。传统博客系统经常把主题、页面结构、插件和内容规则缠在一起。文章本身应该只是文章,最好放在清楚的 content/ 目录里;主题应该负责视觉和布局;RSS、搜索、站点地图、AI 输出这些功能则应该属于系统能力,而不是某个主题顺手实现的东西。

第三是 AI 友好。过去博客主要面向浏览器里的读者,现在公开内容也需要更容易被机器理解。llms.txt、结构化 JSON、搜索索引、清晰的多语言 URL、稳定的 Markdown 内容结构,都会变得越来越重要。

所以我的目标不是“再换一个主题”,而是重新整理一套更适合长期使用的内容发布方式。

为什么自己写

这个问题其实很值得先问:博客系统已经很多了,为什么还要自己写?

一开始我也看过现有方案。直接用 Astro starter、VitePress、Nextra、Nuxt Content、Eleventy 之类,都能做出不错的内容站。但我想要的不是一个单站点模板,而是一层更产品化的封装:

  • 默认只暴露 Markdown、配置和静态资源,不要求用户理解完整 Astro 工程结构。
  • 主题和 renderer 分开,个人博客和商业内容站可以用同一套内容模型。
  • 内建 RSS、sitemap、静态搜索、JSON Feed、posts JSON、llms.txt
  • 支持主语言内容和翻译内容分目录组织。
  • 能作为 npm CLI 使用,而不是每个项目复制一份框架代码。
  • 未来可以复用到公司网站、商业博客、文档站和其他内容型产品。

如果只是给自己的博客换个样式,直接改 Hexo 或换 Astro starter 就够了。但我想验证的是一套更清楚的发布产品层:Markdown 是内容源,Astro 是渲染底座,InkIsle 负责把这些能力包装成简单的工作流。

这也是我选择自己写的原因。不是因为市面上没有成熟方案,而是因为我想要的边界和体验足够具体,自己实现一个最小系统反而更容易把它打磨成自己长期会用的东西。

为什么用 Astro

InkIsle 没有从零实现静态站点生成器,底层选择了 Astro。

原因也很直接。

Astro 默认适合静态输出,可以把 Markdown 预渲染成 HTML;它的构建体系基于 Vite,本地开发和构建性能都很好;Markdown、MDX、静态路由、动态路由、RSS、sitemap、adapter 这些能力都有成熟基础。后续如果真的需要 SSR 或 Edge rendering,也有 adapter 的扩展路径。

我一开始也考虑过偏 Next.js 方向的方案,比如 Vinext 这类更开放的全栈框架探索。但博客系统的第一目标不是运行时应用,而是静态优先、预渲染优先、内容优先。评论、登录态、动态预览这些都可以晚点做,文章页本身应该尽量只是静态 HTML。

所以最终的取舍是:不重写底层构建器,也不把博客做成复杂应用。Astro 做底座,InkIsle 做产品层。

InkIsle 的设计

InkIsle 的核心结构是两个 starter。

默认的 content-only starter 面向普通使用者,项目里只有这些东西:

content/
public/
inkisle.config.mjs
package.json

这意味着一个博客项目不需要看到 astro.config.mjssrc/pages/、布局组件和 renderer 实现细节。日常写作只需要维护 Markdown 内容和配置。

另一个 default starter 是完整 Astro renderer,放在 InkIsle 包里。它负责读取内容、生成页面、套用主题、输出 feed、搜索索引和静态文件。

内容结构大致是这样:

content/posts/my-post.md
content/pages/about.md
content/en/posts/my-post.md
content/en/pages/about.md

主语言内容直接放在 content/posts/content/pages/,翻译内容放在 content/{lang}/ 下。默认情况下,主语言发布到根路径,英文等翻译内容使用语言前缀:

/posts/my-post/
/en/posts/my-post/

这个选择和最初设想有一点调整。最初我想过构建后主语言也带 /zh/ 前缀,后来迁移真实博客时发现,个人博客的主语言路径不带前缀更自然,也更利于保留旧链接和读者习惯。因此 InkIsle 默认主语言无前缀,同时保留 /zh/... 兼容重定向。

已经完成的能力

目前 InkIsle 已经完成了一个可以真实使用的版本,并且发布到了 npm。

现在已经有:

  • inkisle init 创建默认内容站。
  • inkisle init --full 创建完整 Astro 项目。
  • inkisle new postinkisle new page 创建内容。
  • inkisle devinkisle build 等命令跑本地开发、构建和本地预览。
  • inkisle check links 检查构建产物里的站内链接。
  • 个人博客主题 personal
  • 商业内容站主题 business-blog
  • 文章列表、分页、标签页、分类页、自定义页面、搜索页、404。
  • RSS、JSON Feed、/api/posts.json/search-index.json/llms.txt、sitemap、robots.txt。
  • 多语言内容路径。
  • 默认语言无前缀,默认语言前缀路径兼容重定向。
  • PWA manifest、service worker、备案号、百度统计、搜索验证文件、Cloudflare Pages _redirects 等站点级配置。

有些能力还只是配置形态或规划方向,比如 raw Markdown 输出和单篇文章 JSON 输出。它们在最初设计里很重要,但不是迁移个人博客的第一优先级,所以我暂时没有强行把所有想法一次做完。

成品效果

我的个人博客现在已经迁移到 InkIsle。

旧博客内容被整理成 content/posts/,英文翻译文章放在 content/en/posts/。站点配置集中在 inkisle.config.mjs,构建命令也变得很直接:

pnpm run build
pnpm run check:links
pnpm run deploy

迁移后的实际构建结果比我预期更好。当前博客大约生成 400 多个 HTML 页面,构建耗时在几秒级;站内链接检查会扫描数千个链接,用来避免迁移历史文章时留下坏链接。

更重要的是,生成结果不只是网页:

  • /rss.xml 给 RSS 阅读器。
  • /feed.json 给 JSON Feed 读者。
  • /api/posts.json 给结构化消费。
  • /search-index.json 给站内搜索。
  • /llms.txt 给 AI agent 一个入口。
  • /sitemap-index.xml 给搜索引擎。

这正是我想要的新博客系统:不是单纯把 Markdown 变成网页,而是把公开内容整理成多个稳定、机器可读、长期可维护的出口。

如果你也想用

目前 npm 包已经发布,可以直接试用:

npm exec inkisle -- init my-blog
cd my-blog
npm install
npm run dev

创建文章:

npm exec inkisle -- new post "我的第一篇文章" --published

构建:

npm run build

如果你想看到完整 Astro 工程,而不是默认的轻量内容站,可以用:

npm exec inkisle -- init my-full-blog --full

不过我更推荐从默认模式开始。InkIsle 的设计目标就是让多数使用者只关心内容、配置和资源,不必一上来面对完整前端工程。

未来开源计划

InkIsle 现在还处在早期阶段。代码目前主要服务我的个人博客迁移和真实验证,后续我会在这些方面继续补:

  • 完善 README 和使用文档。
  • 补 raw Markdown 和单篇 JSON 输出。
  • 梳理主题 API,决定什么时候支持本地主题和 npm 主题。
  • 增加更清楚的内容质量检查。
  • 改进多语言翻译工作流,至少给 AI 翻译提供明确的路径约定。
  • 为 Cloudflare Pages、GitHub Pages 和自托管部署补示例。
  • 等 API 和主题边界稳定后公开仓库。

我不想太早把它包装成一个“通用框架”。更合理的路径是先服务真实博客,把个人长期使用中遇到的问题都解决掉,再把稳定的部分开源出来。

写博客系统这件事

写博客系统听起来像一种重复造轮子。很多时候也确实是。

但个人博客有一个很特殊的地方:它既是工具,也是自己的写作空间。工具的边界会反过来影响写作习惯、内容整理方式、发布频率和长期维护成本。

2017 年从 WordPress 换到 Hexo,是为了从动态博客走向静态博客。2026 年从 Hexo 换到 InkIsle,是为了从“能生成网页”走向“更适合人和 AI 一起消费的 Markdown 发布系统”。

这个目标听起来不大,但足够具体。对我来说,一个会长期使用、能承载自己内容资产、还能慢慢变成开源产品的博客系统,值得认真写一次。