# 博客 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))); --- ``` 要把它包进完整的站点布局,参见[自定义页面](/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

``` 在 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; ---

Build docs that fly