# 博客
Source: https://blume.ndjp.net/docs/advanced/blog/
English: https://useblume.dev/docs/advanced/blog
在 Blume 里,博客不过是带了一个类型的内容。把某个页面标记为 `type: blog`,Blume 就会自动为它生成 RSS 订阅源和更完整的文章元数据。与[更新日志](/docs/advanced/changelog/)不同,这里没有自动生成的索引页 —— 落地页由你自己编排,设计完全握在你手里。
## 写一篇文章
一篇文章就是一个 frontmatter 里带 `type: blog` 的普通 `.md` 或 `.mdx` 页面。按惯例文章放在 `blog/` 下,但真正起作用的是类型,而不是文件夹:
```mdx blog/introducing-blume.mdx lineNumbers
---
title: Introducing Blume
type: blog
date: 2026-06-22
description: Why we built a markdown-first docs framework.
---
Documentation should be fast, AI-ready, and zero-config — down to not needing a starter template at all. Here's the thinking behind Blume.
```
每篇文章都给一个 `date`,这样订阅源条目才能按最新在前排序并带上 `pubDate`;再给一个 `description` —— 它同时用于订阅源摘要和 SEO。
## RSS 订阅源
Blume 会在 **`/blog/rss.xml`** 处构建博客订阅源,按 `date` 以最新在前排序。订阅源需要一个绝对的站点 URL,所以请设置 [`deployment.site`](/docs/deployment/);随后 Blume 会在每个页面上注入一个 `` 标签,让读者自动发现它。
订阅源默认开启。可在 [`seo.rss`](/docs/discoverability/rss/) 下调整:
```ts blume.config.ts lineNumbers
seo: {
rss: {
enabled: true,
types: ["blog", "changelog"],
limit: 50,
},
}
```
从 `rss.types` 中去掉 `"blog"` 即可不生成该订阅源。
## 结构化数据
开启[结构化数据](/docs/discoverability/structured-data/)后,每篇文章都会以 schema.org **`BlogPosting`** 的形式输出,带上它的描述和发布日期 —— 这是搜索引擎对博客内容所期待的那种更丰富的文章类型。
## 搭建索引
Blume 不会生成 `/blog` 落地页,所以要自己搭一个。最简单的做法是一个内容页面,手工链到每篇文章:
```mdx blog/index.mdx lineNumbers
---
title: Blog
description: News and writing from the team.
---
Why we built a markdown-first docs framework.
```
若想自动列出文章,则在 `pages/blog/index.astro` 添加一个[自定义页面](/docs/advanced/custom-pages/),它从 Astro 的 `docs` 内容集合读取数据,并按路由的 `entryId` 把每个条目与它在 `blume:data` 中的路由配对:
```astro pages/blog/index.astro lineNumbers
---
import { getCollection } from "astro:content";
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";
// 自定义页面只在它自己的路径上渲染,因此这里只列出默认语言下的文章。
// getBlumeCollection 会排除草稿、隐藏页面,以及 i18n 为未翻译页面提供的副本,
// 所以每篇文章只会被列出一次。
const routes = getBlumeCollection(data, {
locale: data.config.i18n?.defaultLocale,
});
const routeByEntry = new Map(routes.map((route) => [route.entryId, route.path]));
const posts = (await getCollection("docs"))
.filter((entry) => entry.data.type === "blog" && routeByEntry.has(entry.id))
.map((entry) => ({
date: entry.data.date,
description: entry.data.description,
href: routeByEntry.get(entry.id),
title: entry.data.title,
}))
.toSorted((a, b) => Number(new Date(b.date)) - Number(new Date(a.date)));
---
{
posts.map((post) => (
-
{post.title}
{post.description}
))
}
```
要把它包进完整的站点布局,参见[自定义页面](/docs/advanced/custom-pages/)。
**[自定义页面](/docs/advanced/custom-pages/)**
挂载博客索引,并从 `blume:data` 读取数据。
**[可发现性](/docs/discoverability/)**
订阅源、Open Graph 图片和结构化数据。
---
# 更新日志
Source: https://blume.ndjp.net/docs/advanced/changelog/
English: https://useblume.dev/docs/advanced/changelog
Blume 开箱即带更新日志。把每次发布写成一个普通内容文件、标记为 `type: changelog`,Blume 就会把每一条都汇入一个自动生成的索引页和一个 RSS 订阅源 —— 不用搭布局、不用维护列表。或者干脆不写这些文件,直接[从 GitHub Releases 引入更新日志](#from-github-releases)。
## 写一条记录
一条更新日志记录就是一个 frontmatter 里带 `type: changelog` 的普通 `.md` 或 `.mdx` 页面。按惯例它们放在 `changelog/` 下,但真正起作用的是类型,而不是文件夹:
```mdx changelog/v1-2-0.mdx lineNumbers
---
title: v1.2.0
type: changelog
date: 2026-06-20
changelog:
version: 1.2.0
category: Features
---
A big batch of components landed this release — columns, frames, trees, and tooltips, plus code groups that render as proper language tabs.
- New `Accordion`, `Expandable`, and `Tooltip` components
- `CodeGroup` tabs with flush code blocks
```
每条记录都给一个 `date`,这样索引和订阅源才能以最新在前排序。YAML 日期不加引号也没关系 —— Blume 会做归一化。
### `changelog` 对象
可选的 `changelog` 对象为索引和订阅源补充更丰富的元数据:
| 属性 | 类型 | 默认值 | 说明 |
| - | - | - | - |
| `changelog.version?` | `string` | - | 发布版本。没有 title 时回退为一个带 v 前缀的标签。 |
| `changelog.category?` | `string` | - | 显示在记录旁的标签,例如 Release、Features、Fixes。 |
| `changelog.date?` | `string` | - | 发布日期。可以写在这里,也可以写在顶层 —— 两者都会进入索引和 RSS 订阅源。 |
## 索引页
一旦有了至少一条 `type: changelog` 记录,Blume 就会自动生成一个 **`/changelog`** 页面。那是一个专注的全宽索引 —— 没有侧边栏,也没有目录 —— 它把每次发布列成一行,最新在前并按年份分组,于是一段很长的历史仍然只是一个很短的页面(远小于 agent 能在单个上下文窗口内读完的体量):
- 该条记录的 **title** 就是这一行的标签 —— 没有 title 时则用 `v{version}`。它链到该记录自己的页面,更新说明全文就在那里渲染。
- `category` 会作为标签渲染在 title 旁边,读者一眼就能分清正式版与预发布版,或者功能与修复。
- 日期遵循配置的 [`dateFormat`](/docs/configuration/),但会去掉这一行所属分组里已经显示过的年份。
- 草稿以及标记了 `sidebar.hidden` 的条目会被跳过。
只有当 `/changelog` 路由上还没有别的东西占据时,这个页面才会出现。要用自己的设计替换它,在 `pages/changelog.astro` 添加一个[自定义页面](/docs/advanced/custom-pages/) —— 它会接管该路由,Blume 也就停止生成默认索引。旧时间线所基于的 `` 组件仍然随包提供,供想在自定义页面里内联渲染更新说明时使用。它不是 MDX 组件之一,所以要在 `.astro` 页面里自行导入:`import Update from "blume/components/content/Update.astro";`。指向 `/changelog` 的头部[标签页](/docs/content/navigation/)会打开这个索引 —— 不需要 `href`。
## 从 GitHub Releases 引入 [#from-github-releases]
与其手写记录,不如把内置的 [`githubReleases()` 源](/docs/content/sources/github-releases/)指向一个仓库,于是每次发布都会变成一条 `type: changelog` 记录 —— 同样的时间线和订阅源,内容直接来自你已经在发布的那些 release。Blume 自己的[更新日志](/docs/advanced/changelog/)就是这么搭起来的:
```ts blume.config.ts
import { filesystem, githubReleases } from "blume/sources";
content: {
sources: [
filesystem({ root: "content" }),
githubReleases({
prefix: "changelog",
owner: "acme",
repo: "sdk",
}),
],
}
```
release 的名字成为 title,它的 tag 成为 `changelog.version`,发布日期则决定时间线的排序。每个发布页还会得到一段独特的 meta description,由它的更新说明概括而来 —— 去掉 markdown、去掉小节标题和 changeset 的提交哈希前缀、裁剪到 [`blume audit`](/docs/cli/audit/) 检查的搜索摘要长度 —— 而不是回退到站点描述。私有仓库通过 `GITHUB_TOKEN` 环境变量认证。全部选项见 [GitHub Releases](/docs/content/sources/github-releases/)。
## RSS 订阅源
Blume 还会在 **`/changelog/rss.xml`** 处构建更新日志订阅源,按 `date` 以最新在前排序。订阅源需要一个绝对的站点 URL,所以请设置 [`deployment.site`](/docs/deployment/);随后 Blume 会在每个页面上注入一个 `` 标签,让读者自动发现它。
订阅源默认开启。可在 [`seo.rss`](/docs/discoverability/rss/) 下调整:
```ts blume.config.ts lineNumbers
seo: {
rss: {
enabled: true,
types: ["blog", "changelog"],
limit: 50,
},
}
```
从 `rss.types` 中去掉 `"changelog"` 即可不生成该订阅源,同时保留时间线。
## 结构化数据
开启[结构化数据](/docs/discoverability/structured-data/)后,每条更新日志记录都会以 schema.org **`TechArticle`** 的形式输出,带上它的描述和发布日期,于是搜索引擎可以把各个发布当作带日期的文章来索引。
**[Frontmatter](/docs/content/frontmatter/)**
完整的更新日志 frontmatter schema。
**[自定义页面](/docs/advanced/custom-pages/)**
用你自己的布局替换生成的时间线。
---
# 自定义页面
Source: https://blume.ndjp.net/docs/advanced/custom-pages/
English: https://useblume.dev/docs/advanced/custom-pages
Blume 站点的大部分内容是 Markdown,但有时你需要一条*不是*文档的路由 —— 落地页、定价页、手工搭的博客或更新日志索引,或者一个交互式仪表盘。在你的 **pages** 目录下放一个 `.astro` 文件,Blume 就会把它挂载成一条真正的路由,与你的内容并列。
## 添加页面
在项目根目录创建一个 `pages/` 文件夹,并加入一个 `.astro` 文件:
```astro pages/pricing.astro lineNumbers
---
import data from "blume:data";
---
Pricing for {data.config.title}
```
`blume dev` 会立刻认出它,`blume build` 则会把它预渲染成静态 HTML。文件夹名可通过 [`content.pages`](/docs/configuration/) 配置(默认 `"pages"`)。
自定义页面在磁盘上保留原来的位置,因此相对导入、组件导入以及 [`getStaticPaths`](https://docs.astro.build/en/reference/routing-reference/#getstaticpaths) 的行为都与普通 Astro 项目完全一致 —— Blume 把每个文件原地挂载,而不是复制一份。
## 文件与路由
每个文件在 pages 目录下的路径就是它的路由。`index` 映射到上级目录,动态的 `[param]` 段会被保留:
| 文件 | 路由 |
| --- | --- |
| `pages/pricing.astro` | `/pricing` |
| `pages/blog/index.astro` | `/blog` |
| `pages/blog/[slug].astro` | `/blog/:slug` |
| `pages/changelog.astro` | `/changelog` |
自定义页面在同一路径上会**胜出**于自动生成的路由。比如加上 `pages/changelog.astro`,就把 Blume 的[更新日志时间线](/docs/advanced/changelog/)换成了你自己的。
Blume 无法枚举一条动态 `[param]` 路由会服务哪些路径,因此这类页面不会进入[站点地图](/docs/discoverability/sitemap-and-robots/)、不会生成 [Open Graph 卡片](/docs/discoverability/open-graph/),[`blume validate`](/docs/cli/validate/) 也不认识它们 —— 链到它们的链接会被报成失效。要让它们拥有分享图,可以给 `PageLayout` 传 `ogImage`;或者为每条路径各配一个静态文件(用 `pages/compare/mintlify.astro` 而不是 `pages/compare/[tool].astro`),这样三样都能拿到。
## 读取站点数据
导入 `blume:data`,即可读到与站点其它部分相同的已解析配置、导航、路由和订阅源:
```astro pages/all-pages.astro lineNumbers
---
import data from "blume:data";
---
All pages
{
data.routes
.filter((route) => route.indexable)
.map((route) => (
-
{route.title}
))
}
```
在 Blume 项目里这个模块的类型是自动的。你也可以显式引入它的类型定义 —— 用于带类型的辅助函数、props 或你自己的 tsconfig —— 即 `import type { BlumeData } from "blume"`:
```ts
import type { BlumeData, BlumeRoute } from "blume";
const indexable = (data: BlumeData): BlumeRoute[] =>
data.routes.filter((route) => route.indexable);
```
该模块暴露以下内容:
| 属性 | 类型 | 默认值 | 说明 |
| - | - | - | - |
| `config` | `BlumeDataConfig` | - | 已解析的站点设置:title、description、logo、favicon、appleIcon、banner、theme、site、repoUrl、github(owner、repo、host 以及 REST API 基础地址 —— 未设置时为 null)、search、i18n、mcp、assistant、og、analytics、feedback、structuredData、toc、codeThemes、codeWrap 和 imageZoom。 |
| `navigation` | `Navigation` | - | 从你的内容(默认语言)推导出来的侧边栏、标签页和选择器。 |
| `navigationByLocale` | `Record` | - | 按语言代码索引的各语言导航树。没有配置 i18n 时为空。 |
| `routes` | `BlumeRoute[]` | - | 每一个内容页面:`{ id, path, title, locale, indexable, hidden, draft, fallback, editUrl, lastModified, alternates, collection, entryId }`。 |
| `feeds` | `BlumeFeed[]` | - | 生成的 RSS 订阅源:`{ href, title }`。 |
| `fontCssVars` | `FontHead[]` | - | 供 Astro 的 `` 组件在 head 中使用的已配置字体,每个字族一项:`{ cssVariable, preloadWeights, preloadSubsets? }`。 |
| `ui` | `UIStrings` | - | 默认语言的已解析 UI 文案(搜索、侧边栏和页脚标签)。 |
| `uiByLocale` | `Record` | - | 按语言代码索引的各语言 UI 文案。没有配置 i18n 时为空。 |
`routes` 带有页面元数据,但不含 `type`、`date` 这类 frontmatter。要按内容类型过滤出一份列表 —— 比如博客或更新日志索引 —— 把它与 Astro 的 `docs` 内容集合搭配使用,后者保存着 frontmatter:
```astro pages/blog/index.astro lineNumbers
---
import { getCollection } from "astro:content";
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";
// 在 i18n 下,每个语言都会为每个条目提供一条路由(它的译文,或未翻译页面
// 的副本),它们共享同一个条目 id。这里只用默认语言的路由做键,这样每篇
// 文章只会在一种语言里出现一次链接。
const routes = getBlumeCollection(data, {
locale: data.config.i18n?.defaultLocale,
});
const routeByEntry = new Map(routes.map((route) => [route.entryId, route.path]));
const posts = (await getCollection("docs"))
.filter((entry) => entry.data.type === "blog" && routeByEntry.has(entry.id))
.map((entry) => ({
description: entry.data.description,
href: routeByEntry.get(entry.id),
title: entry.data.title,
}));
---
{
posts.map((post) => (
-
{post.title}
{post.description}
))
}
```
## 运行时辅助函数
`blume/runtime` 打包了常用的数据模式,你不必去碰 `blume:data` 的内部实现。
**`getBlumeCollection(data, query?)`** 用来挑选内容路由 —— 可按集合、语言或路径前缀过滤,排除草稿、隐藏页面和 i18n 回退副本,并按路径排序 —— 这正是一个自定义索引所需要的:
```astro pages/blog/index.astro lineNumbers
---
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";
const posts = getBlumeCollection(data, { prefix: "/blog" });
---
```
**``** 在自定义页面里渲染某个内容条目的正文,Blume 内置的 MDX 组件(callout 提示框、卡片、步骤……)已经接好 —— 适合在落地页上重点展示某篇文档,或搭建一个展示真实内容的定制索引:
```astro pages/index.astro lineNumbers
---
import BlumePage from "blume/components/BlumePage.astro";
import data from "blume:data";
import { getBlumeCollection } from "blume/runtime";
const [intro] = getBlumeCollection(data, { prefix: "/docs" });
---
{intro && }
```
传入 `components` 可以加入你自己的覆盖项或交互岛(它们位于生成的运行时中,默认不会导入),传入 `collection` 则可以从 `"docs"` 以外的集合读取。
## 使用站点布局 [#using-the-site-layout]
`RootLayout` 通过把自定义页面包进与生成页面相同的三栏网格,为它提供完整的文档外壳 —— 头部、侧边栏、搜索、目录和主题。但对落地页或营销页来说,那个网格是累赘,所以这时该用 **`PageLayout`**:它提供文档骨架、头部、主题和字体,然后是一个全宽的 ``(没有侧边栏、没有正文排版、没有目录)。可选的 `footer` 插槽会渲染在 `` 之后:
```astro pages/index.astro lineNumbers
---
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
import Footer from "./_home/Footer.astro";
const { config } = data;
---
```
自定义页面拿到的头部与文档页面拿到的是同一个,因此住在里面的那些外壳也都跟了过来:搜索、主题切换,以及在配置了[助手](/docs/configuration/assistant/)时的助手入口。它们都不需要逐页接线。传 `assistantEnabled={false}` 可以在保留其它页面助手入口的同时,只让某一个页面不显示它。
语言切换器是例外。自定义页面只在自己的路由上提供服务,因此 Blume 无法知道其它哪些语言也有它的版本,除非你显式传入,否则头部不会显示切换器:`localeSwitch` 为每种语言接收一个条目 —— `{ code, label, dir, href, current, untranslated }` —— 指向你为那种语言构建的页面。
[面向 agent 发现的 head 链接](/docs/discoverability/agent-discovery/)同样会带上:布局会读取已解析的配置,因此自定义页面无需传 prop 就带有与文档页面相同的 `describedby`、`ai-catalog` 和 `ard` 链接。首页还会把自己的 `/index.md` Markdown 镜像作为 `text/markdown` 备选链接公布出去,因为这个镜像总是存在;其它自定义页面没有镜像,因此也不会公布。传 `discovery={null}` 可以让某一个页面不带这些链接。
传 `transparentHeader` 可以让头部起始时是透明的、外壳部分为白色,这样它能压在页面顶部的深色 hero 之上;页面一滚动它就变回常见的毛玻璃条,搜索对话框也始终保持页面自己的配色。要看出这个效果,hero 必须延伸到头部下方 —— 用头部的高度(`-mt-16`)把它提上去,并相应补上顶部内边距。
传入 `siteUrl`(以及 `ogEnabled`)会自动推导出页面的 `canonical` 和一张生成的 `og:image`:Blume 会为每个静态自定义页面渲染一张 Open Graph 卡片(动态 `[param]` 页面除外)—— 包括首页这个最常被分享的 URL —— 它在 `/og/.png` 处提供(`/` 对应 `/og/index.png`)。首页卡片的标题是站点标题,副标题是站点描述;层级更深的页面则用路径的最后一段作为标题。显式设置 `ogImage` 或 `canonical` 即可覆盖其中任意一项。`ogImage` 接受一个相对于根的路径 —— `public/` 里的文件,会依据 [`deployment.site`](/docs/deployment/) 解析成爬虫所需的绝对 URL —— 或者一个外部 URL,后者会原样透传:
```astro pages/index.astro lineNumbers
```
只有这一个页面会变 —— 其它每条路由都保留自己生成的卡片 —— 因此这是给首页单独配一张定制分享图的办法。生成的卡片会自行向爬虫声明尺寸和替代文字;换成你自己的 `ogImage` 时,请一并传入 `ogImageAlt` 和 `ogImageSize`,让分享卡得到同样的待遇。
页面还会输出 schema.org JSON-LD —— 与文档页面所带的是同一份 `WebSite` 图谱 —— 这样首页(通常就是一个自定义页面)就不会成为唯一缺少结构化数据的 URL。传 `structuredDataEnabled={config.structuredData}` 可以让它与 [`structuredData`](/docs/discoverability/structured-data/) 配置保持一致,或传 `structuredDataEnabled={false}` 为某一个页面关掉它。
`page.title` 会被逐字用作文档标题(不会追加 `- siteTitle` 后缀),因为营销页通常有自己的标题。若想让自定义页面拥有完整的文档外壳 —— 侧边栏、目录等等 —— 就把它包进生成页面所用的 `RootLayout`。所需 props 可以直接从 `blume:data` 取:
```astro pages/pricing.astro lineNumbers
---
import RootLayout from "blume/components/layout/RootLayout.astro";
import data from "blume:data";
---
Pricing
```
:::note
`RootLayout` 属于生成的运行时,因此它的 props 可能随版本变化。当你想要一个完全属于自己的布局时,[`blume eject`](/docs/configuration/customization/) 会把 `.blume/` 变成一个完全归你所有的标准 Astro 项目。
:::
## 404 页面 [#404-page]
Blume 开箱即带一个默认的**未找到**页面:一条居中的 "404" 信息,外面裹着站点外壳(头部、搜索、主题),任何未匹配的 URL 都会得到它。`blume build` 把它写成 `404.html`,静态托管会自动提供这个文件,`blume dev` 则在遇到未知路由时显示它。信息下方有一个 **接下来可以看看** 列表,链到每个顶层板块,以及存在时的 `sitemap.xml` 和 [`llms.txt`](/docs/discoverability/llms-txt/) 索引,这样读者 —— 或者跟着过期 URL 而来的 agent —— 总有条回来的路。
这个页面还有一对孪生页面:`/404.md` 的 Markdown 版和 `/404.json` 的 JSON 版([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) 的 problem details),带着同样的恢复链接(设置了 [`deployment.site`](/docs/deployment/) 之后就是绝对 URL),开启 JSON API 时还会带上 [`openapi.json`](/docs/discoverability/json-api/) 的描述。在 [Vercel 或 Cloudflare 的服务端构建](/docs/deployment/)上,对一个缺失页面的请求如果带上 [`Accept: text/markdown`](/docs/discoverability/markdown/),会得到 Markdown 正文和 `404` 状态码,而不是 HTML 外壳;带上 `Accept: application/json` 则会得到 problem document —— 于是 agent 永远不必去解析一整页外壳才知道该往哪儿走。在 Vercel 上,没有任何文件支撑的 `.md` 或 `.json` URL 同理。
要换成自己的页面,添加一个 `pages/404.astro`。它拥有 `/404` 路由的方式与 `pages/changelog.astro` 接管更新日志一样 —— 你的页面胜出,默认页面连同它的 `/404.md` 和 `/404.json` 孪生页面一起被丢弃。像构建任何其它自定义页面那样构建它,用 `PageLayout` 或 `RootLayout`:
```astro pages/404.astro lineNumbers
---
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
---
This page took a wrong turn
Back to home
```
想保留默认设计、只换文案 —— 包括为其它语言换文案 —— 可以通过 `i18n.ui` 覆盖 `notFound` 的 [UI 文案](/docs/content/i18n/)(`title`、`description`、`home`)。
## 交互式页面
自定义页面就是普通的 Astro,因此你可以用一条 hydration 指令放进 React(或任何框架)的[交互岛](/docs/configuration/customization/)。项目里一旦出现 `.tsx` 或 `.jsx` 文件,React 就会自动启用。
**[自定义](/docs/configuration/customization/)**
组件覆盖、React 交互岛、registry 和导出。
**[博客](/docs/advanced/blog/)**
撰写文章并搭建自定义博客索引。
---
# 技能
Source: https://blume.ndjp.net/docs/advanced/skills/
English: https://useblume.dev/docs/advanced/skills
Blume 随包提供[agent 技能](https://docs.claude.com/en/docs/claude-code/skills) —— 一套操作手册,教编码 agent(Claude Code、Codex、Cursor)如何完成 Blume 式的活儿,而你不必逐条解释。它们在 GitHub 上位于仓库的 `skills/` 目录,同时也打包在已安装的包的 `skills/` 下,因此任何 agent 都可以直接指向某个 `SKILL.md`。
## Blume
核心技能。它教会 agent Blume 是什么,以及如何生成脚手架、编写和配置一个站点 —— 从文件系统推导出来的导航、配置 schema、内容组件 —— 并把 agent 指向随安装包一起分发的完整文档(`blume` 包内的 `docs/` 目录,路径取决于你的包管理器把它装到了哪里 —— 在 pnpm monorepo 中是依赖它的那个 workspace 的 `node_modules`,而不是仓库根目录)。在任何由 agent 帮你编写文档的项目里都可以安装它:
```bash
npx skills add haydenbleasel/blume
```
## 迁移
`blume-migrate` 把一个已有的文档站迁移到 Blume:Mintlify、Fumadocs、Docusaurus、Starlight、Nextra,或任何其它框架。它以地道的 Blume 为目标,而不是逐行照搬:为每个具名框架提供一份映射参考,为每一个会变动的 URL 配一个重定向,并报告它丢弃了什么。运行它最省事的方式是 [`blume migrate`](/docs/migrating/),它会检测你的框架,并针对随包分发的那份打开 Codex 或 Claude Code。想在别的 agent 里使用它,请安装:
```bash
npx skills add haydenbleasel/blume --skill blume-migrate
```
## 让文档自我更新
`blume-update-docs` 让你的文档与它们所描述的产品保持同步。每次运行 —— 通常由你在 agent 运行器里配置的定时任务触发 —— 它会把最近合并的 PR、更新日志、配置 schema 和 CLI 帮助与文档内容逐一比对,只更新那些事实上已经过时的页面(feature flag 后面的工作会被忽略),用 `blume build` 验证,然后开启或更新一个 `blume/*` pull request。如果什么都没有过时,它会报告一次干净的空操作,而不是开一个吵闹的 PR。
```bash
npx skills add haydenbleasel/blume --skill blume-update-docs
```
Blume 不托管这套自动化 —— 把这个技能接进 Claude Code 的定时任务、Codex 或 Cursor 的自动化,或者干脆用 cron,并授予它读取仓库历史和开 PR 的权限。一个典型的每周提示词:
```text
使用 blume-update-docs 技能。审阅过去 7 天内合并的 PR,把它们与文档内容对比。忽略 feature flag 后面的工作。如果文档需要更新,就更新它、验证文档能构建,并开一个 blume/* 的 PR。如果不需要,就报告你检查了哪些内容,不要开 PR。
```
## 为你自己的站点编写技能 [#writing-your-sites-skill]
`blume-write-skill` 会为你自己的文档编写 [agent 技能](/docs/discoverability/agent-discovery/):一份以文档为依据的 `SKILL.md`,教编码 agent 使用你的产品 —— 安装配置、核心概念、常见任务和坑 —— 并把每个主题链接到它所在的页面。它取代了 Blume 在 `/skill.md` 处生成的页面地图。运行它最省事的方式是 `blume skill`,它会算出这个技能该放在哪里,并针对随包分发的那份打开 Codex 或 Claude Code:
```bash
blume skill --claude # 或 --codex
```
想在别的 agent 里使用它,请安装:
```bash
npx skills add haydenbleasel/blume --skill blume-write-skill
```
---
# 命令行
Source: https://blume.ndjp.net/docs/cli/
English: https://useblume.dev/docs/cli
```bash
blume [options]
```
## 命令
| 命令 | 说明 |
| --- | --- |
| `blume init [dir]` | 生成项目脚手架(默认交互式)。 |
| `blume dev` | 启动带热重载的开发服务器。 |
| `blume build` | 构建静态(或服务端)站点。 |
| `blume preview` | 预览上一次构建。 |
| `blume add [item]` | 从 registry 安装一个源组件(不带 item 时列出可用项)。 |
| `blume sync` | 重新拉取远程内容源并重新生成。 |
| `blume eject` | 把运行时导出为一个独立的 Astro 应用。 |
| `blume check` | 用 `astro check` 对站点做类型检查。 |
| [`blume doctor`](/docs/cli/doctor/) | 诊断配置与内容问题。 |
| [`blume validate`](/docs/cli/validate/) | 校验内容中的所有链接。 |
| [`blume audit`](/docs/cli/audit/) | 审查构建产物中的 SEO 与站点健康问题。 |
| [`blume eval`](/docs/cli/evals/) | 测试文档:让一个 agent 仅凭文档回答你的问题。 |
| [`blume translate`](/docs/cli/translate/) | 用本地 agent CLI 把文档翻译到已配置的语言。 |
| [`blume version [id]`](/docs/cli/version/) | 把当前文档冻结为一个归档版本(不带 id 时列出已配置的版本)。 |
| [`blume skill`](/docs/discoverability/agent-discovery/) | 用 Codex 或 Claude Code 为你的文档编写 agent skill,替代自动生成的那一份。 |
| [`blume migrate [source]`](/docs/migrating/) | 用 Codex 或 Claude Code 把 Mintlify、Fumadocs、Docusaurus、Starlight 或 Nextra 站点迁移到 Blume。 |
| [`blume upgrade`](/docs/upgrading/) | 升级到新的主版本:先升级 `blume`,再列出剩余的配置改动,或把它们交给 Codex 或 Claude Code。 |
## 通用选项 [#common-flags]
- `blume init` — 在终端里,它会引导你回答几个问题(项目建在哪里、站点名称、模板、内容源);下面的每个选项都预先回答了其中一个问题。
- `blume init --yes` — 跳过所有提问,用默认值生成脚手架(在 CI 中或 stdin 不是终端时也是这个行为)。
- `blume init --content-dir ` — 设置内容目录(默认 `docs`)。
- `blume init --template docs|api|sdk|changelog` — 从一个起始模板生成脚手架(用 API 参考、SDK 或更新日志,替代普通的文档种子)。
- `blume init --package-manager npm|pnpm|yarn|bun` — 用指定的包管理器安装依赖,并打印对应的后续步骤(默认:运行 `blume init` 的那个)。
- `blume init --no-install` — 只写文件、跳过依赖安装,适用于 CI 或自定义的依赖流程。默认情况下 `blume init` 会运行包管理器的安装命令,让项目开箱即可运行;如果安装失败,脚手架会被保留、重试命令会被打印出来,退出码非零。
- `blume init --eject` — 先生成脚手架,再导出为一个独立的 Astro 项目(配合 `--no-install` 时,依赖装好后会改为引导你执行 `blume eject`)。
- `blume dev --host --port --open`
- `blume dev --content-dir ` — 扫描另一个内容目录,无需改动 `blume.config.ts`。
- `blume dev --debug` — 输出详细的 Astro/Vite 日志,便于排查问题。
- `blume dev --preview` / `blume build --preview` — 包含草稿和未发布的 CMS 内容。
- `blume build --no-strict` — 即使有诊断错误也照常构建。默认情况下 `blume build` 只要遇到任何错误级诊断就失败(退出码 1),因为 frontmatter 校验未通过的页面会被从产物中丢弃;加上 `--no-strict` 后构建会成功,并报告有多少页面被丢弃。`blume dev --strict` 让开发模式采用同样的快速失败行为。
- `blume build --analyze` — 构建后打印客户端 JavaScript 各包的体积(从大到小)。
- `blume build --budget-js --budget-css ` — 当客户端 JavaScript/CSS 总量超出预算时让构建失败,把性能目标变成一道 CI 关卡。
- `blume build --isolated` — 构建到一个一次性的 `.blume-verify/` 运行时(以及它自己的 `dist/`)中,而不是 `.blume/`,这样正在运行的 `blume dev` 服务器和你真正的 `dist/` 都不会被影响。参见[开发服务器运行时如何校验](#verifying-while-the-dev-server-runs)。
- `blume preview --host --port ` — 绑定预览服务器。
- `blume sync --force` — 先丢弃缓存快照,再重新拉取远程源。
- `blume sync --preview` — 包含草稿和未发布的 CMS 内容。
- `blume sync --strict` — 遇到诊断即失败。
- `blume add - --force` — 覆盖已存在的文件。
- `blume check --preview` — 检查时包含草稿和未发布的 CMS 内容。
- `blume check --strict` — 内容诊断与类型错误都导致失败。
- `blume check --isolated` — 在一次性的 `.blume-verify/` 运行时中做类型检查,使正在运行的 `blume dev` 服务器不受影响。参见[开发服务器运行时如何校验](#verifying-while-the-dev-server-runs)。
- `blume eject --yes` — 跳过确认提示。
- `blume eject --force` — 在已经导出的应用上再次导出,覆盖其 `astro.config.mjs` 和 `src/`。
有独立页面的命令会把自己的全部选项都列在那一页:[`blume doctor`](/docs/cli/doctor/)、[`blume validate`](/docs/cli/validate/)、[`blume audit`](/docs/cli/audit/)、[`blume eval`](/docs/cli/evals/)、[`blume translate`](/docs/cli/translate/) 和 [`blume version`](/docs/cli/version/)。`blume validate`、`blume doctor`、`blume audit`、`blume eval` 和 `blume translate` 接受 `--json`,把机器可读的结果打印到 stdout,供 CI 和编辑器集成使用(诊断的数据结构见 [validate](/docs/cli/validate/));`build`、`check` 和 `dev` 只向终端输出。每个命令都会拒绝自己不接受的选项,并给出最接近的那一个作为建议、同时列出它真正接受的选项,因此像 `--isolatd` 这样的拼写错误会直接失败,而不会被悄悄忽略。
## 开发服务器运行时如何校验 [#verifying-while-the-dev-server-runs]
`blume dev` 提供的是一个以生成的 `.blume/` 运行时为根的实时 Astro 服务器,并在每次改动后重新生成它。`blume build` 和 `blume check` 重新生成的是*同一个* `.blume/`,因此在开发服务器运行时执行其中任何一个都会把它弄坏——两者都会报错并以非零码退出:
```
A `blume dev` server is running at http://localhost:4321; building would
corrupt its .blume runtime. Reuse that server, stop it first, or re-run with
--isolated to build/verify against .blume-verify without touching it.
```
`--isolated` 就是那个逃生舱。它把整个生成的运行时(对 `build` 来说还有它输出的 `dist/`)挪到同级的 `.blume-verify/` 目录,因此校验过程绝不会写入开发服务器(或你真正的 `dist/`)所依赖的任何东西:
```bash
# 在第二个终端里,`blume dev` 正在运行时:
blume check --isolated # 快:只对 .astro/config 的改动做类型检查
blume build --isolated # 彻底:把生产构建完整渲染到 .blume-verify/dist
```
`check --isolated` 是快捷路径(Astro 类型与模板诊断,不产出 `dist/`);`build --isolated` 更重,还会捕捉运行时渲染错误。隔离构建会跳过部署后的步骤(搜索索引、托管服务同步、`llms.txt`、sitemap/robots、重定向)——一次校验只需要确认站点能编译、能渲染,不需要把它发布出去。`--analyze` 以及 `--budget-js`/`--budget-css` 两道关卡仍然会跑,只是以隔离产物为基准。Blume 会自动把 `.blume-verify/` 加进你的 `.gitignore`。
当你想让开发服务器一直开着、同时又需要编码 agent 校验改动时,这一点尤其有用。要让普通的 `blume build`/`blume check` 不加这个选项也能自动隔离——比如在 agent 的 shell 里——把 `BLUME_RUNTIME_DIR` 设成要使用的运行时目录:
```bash
export BLUME_RUNTIME_DIR=.blume-verify
```
## 类型检查
`blume check` 会对你的项目运行 [`astro check`](https://docs.astro.build/en/reference/cli-reference/#astro-check)。它会重新生成 `.blume` 运行时、同步 Astro 的内容类型,然后报告所有 TypeScript 错误——无论是在你的 `blume.config.ts` 里、在自定义的 `.astro` 页面里,还是在这些页面导入的组件中。有错误时它以非零码退出,因此可以直接当作 CI 中的 `typecheck` 步骤:
```json title="package.json"
{
"scripts": {
"typecheck": "blume check"
}
}
```
在项目根目录添加一个继承 Astro 配置的 `tsconfig.json`,这样手写的页面才能解析 `blume/*` 导入以及 `blume:data` 这类虚拟模块:
```json title="tsconfig.json"
{
"extends": "astro/tsconfigs/strict",
"include": [".blume/.astro/types.d.ts", "**/*"]
}
```
没有项目级 `tsconfig.json` 时,只会检查生成的运行时。
---
# 审查
Source: https://blume.ndjp.net/docs/cli/audit/
English: https://useblume.dev/docs/cli/audit
[`blume validate`](/docs/cli/validate/) 读的是你的*内容*;`blume audit` 读的是*构建产物*。它在构建之后遍历 `dist/` 中的 HTML,报告 SEO 与站点健康方面的问题——标题、meta description、canonical、Open Graph 与 X 卡片、标题层级、hreflang、图片、站点地图、`robots.txt` 和结构化数据。
由于站点是 Blume 构建的,每条发现都会指明源文件**以及能修复它的那一行 frontmatter**,而不只是爬虫能看到的 URL:
```
⚠ Meta description too long or too short 5 pages
/docs/configuration/export content/docs/configuration/export.mdx:3
fix: Rewrite `description` in the frontmatter to fit the length range.
```
在构建之后运行它:
```bash
blume build
blume audit
```
发现按检查项分组,而不是逐页罗列,因此这份报告读起来就像一份待办清单。用 `--verbose` 可以把每个受影响的页面连同完整细节一起展开,用 `--only`/`--skip` 则可以一次只处理一个类别。`blume audit --list-checks` 会打印完整的检查项目录,它与本页末尾的[检查项目录](#check-catalog)是同一份——每条发现的 `docsUrl` 都链接到那里对应的条目。
## 选项
- `--fail-on error|warning|info` — CI 关卡:达到该级别或更高级别时以非零码退出。默认为 `error`;`--strict` 是 `--fail-on warning` 的别名。
- `--url ` — 同时探测一个线上部署的状态码、响应头和重定向链。
- `--external` — 通过网络探测出站链接。
- `--only ` / `--skip ` — 把报告限定在这些检查项或类别之内(或把它们排除),逗号分隔。如果某个词既不对应检查项也不对应类别,就会报错并给出最接近的那一个,因此拼写错误不会悄悄把报告清空。
- `--list-checks` — 打印审查能报告的每一个检查项,然后退出。
- `--verbose` — 列出每一个受影响的页面及每条发现的完整细节,而不只是前几条。
- `--json` — 把报告以 JSON 输出到 stdout。
- `--codex` / `--claude` — 把发现交给 Codex 或 Claude Code 交互式地修复。
## 让 CI 失败
退出码就是约定。默认情况下 `blume audit` 只在 error 上失败——也就是那些确定坏掉的问题,比如链到一个从未构建出来的页面、重定向成环、站点地图无效。仅供参考的发现(描述过短、title 重复)属于 warning,不会让构建失败:
```bash
blume audit # 在 error 上失败
blume audit --fail-on warning # 在 warning 上也失败
```
## 检查线上部署
有些事只有真实服务器才能告诉你:`dist/` 中确实存在的页面会不会因为一条错误的重写规则而真的返回 404、响应有没有被压缩,以及某个 `X-Robots-Tag` 响应头是否正在悄悄地把一个 HTML 看起来完全正常的页面排除在索引之外。把审查指向一个部署即可加上这些检查:
```bash
blume audit --url https://docs.example.com
blume audit --url https://docs.example.com --external # 同时探测出站链接
```
出站链接是分级判定而不是一律判失败:404 是你可以修好的失效链接,而 403 或 5xx 通常是限流或别人的服务故障,因此只报为警告。
## 用 agent 修复发现的问题
如果你在用 [Codex](https://developers.openai.com/codex/cli) 或 [Claude Code](https://claude.com/claude-code),审查可以把发现直接交给它:
```bash
blume audit --codex # 或 --claude
```
它会把完整的 JSON 报告——包含每一个受影响的页面,而不只是终端里那三页预览——写到一个文件,然后交互式打开 agent,并附上一段提示,逐条引导它处理发现:编辑每条发现指明的源文件、采用它建议的修复,然后重新运行 `blume build` 和 `blume audit`,直到报告干净。这个会话是有意设计成交互式的:你通过 agent 自己的权限流程审阅它的修改,而且 agent 会被明确告知,绝不能靠删掉内容来"修好"一条发现。
`--only` 和 `--skip` 收窄交接内容的方式与收窄报告完全一样,因此你可以一次只交给它一个类别。
## 它检查什么、不检查什么
这套检查项有意比通用 SEO 爬虫更窄。这类爬虫报告的很多东西根本不会发生在 Blume 站点上——它从不输出 `rel=nofollow`,Vite 按内容哈希生成的包也不会缺失或重定向——把它们永远报成 0 只会教会你忽略这份报告。
值得明说的局限:
- **结构化数据**只做格式合法性校验(有效的 JSON、有 `@context`、每个节点都有 `@type`)。Blume 不会对照完整的 schema.org 词表或 Google 的富媒体结果规则做校验。
- **对比度**只针对你的主题配置所设置的颜色来检查,而不是逐个元素检查。审查不会渲染页面,因此你在 MDX 或 `theme.css` 里手写的颜色不会被测量。
- **Core Web Vitals**不在检查范围内。它们需要真实浏览器,而一个悄悄什么都没测出来的指标比没有这个指标更糟——因此 `blume audit` 只报告它*能*离线看到的布局偏移原因(没有 `width`/`height` 的图片、过大的资源),其余部分暂时不碰。
审查没有运行的任何检查都会被报为跳过,而不是悄悄判定通过:
```
⊘ network skipped — pass --url (11 checks)
⊘ external skipped — pass --external (2 checks)
```
## 检查项目录 [#check-catalog]
`blume audit` 能报告的每一个检查项,按类别分组,并列出它的默认级别、所属的运行层级以及报告建议的修复方式。`--only` 和 `--skip` 接受这些 id(不写 `BLUME_AUDIT_` 前缀也行)或某个类别的键。键名并不总是分类标题本身,所以请用这张表:`blume audit --only i18n`,而不是 `--only Internationalization`。
| 类别 | 键 |
| --- | --- |
| 无障碍 | `accessibility` |
| 内容 | `content` |
| 重复内容 | `duplicates` |
| 可索引性 | `indexability` |
| 链接 | `links` |
| 重定向 | `redirects` |
| 社交卡片 | `social` |
| 国际化 | `i18n` |
| 资源 | `assets` |
| 站点地图 | `sitemap` |
| robots.txt | `robots` |
| AI 可发现性 | `ai` |
| 结构化数据 | `structured-data` |
| 线上部署 | `network` |
### 内容
#### 缺少 title 标签或为空 [#title-missing]
`BLUME_AUDIT_TITLE_MISSING` · error · 来自构建后的 HTML
修复:在该页面的 frontmatter 中添加 `title`。
#### 存在多个 title 标签 [#title-multiple]
`BLUME_AUDIT_TITLE_MULTIPLE` · error · 来自构建后的 HTML
修复:从页面的布局或 MDX 中删掉多余的 ``。
#### title 过长或过短 [#title-length]
`BLUME_AUDIT_TITLE_LENGTH` · warning · 来自构建后的 HTML
修复:重写 frontmatter 中的 `title`,使其符合长度范围。
#### 缺少 meta description 或为空 [#description-missing]
`BLUME_AUDIT_DESCRIPTION_MISSING` · warning · 来自构建后的 HTML
修复:在该页面的 frontmatter 中添加 `description`。
#### 存在多个 meta description 标签 [#description-multiple]
`BLUME_AUDIT_DESCRIPTION_MULTIPLE` · error · 来自构建后的 HTML
修复:从页面的布局或 MDX 中删掉多余的 description ``。
#### meta description 过长或过短 [#description-length]
`BLUME_AUDIT_DESCRIPTION_LENGTH` · warning · 来自构建后的 HTML
修复:重写 frontmatter 中的 `description`,使其符合长度范围。
#### 缺少 H1 标签或为空 [#h1-missing]
`BLUME_AUDIT_H1_MISSING` · warning · 来自构建后的 HTML
修复:给该页面一个 `title`——Blume 会把它渲染成页面的 `