Gatsby RSS Feed 生成实战:以 gatsby-plugin-feed 与 Markdown 博客内容为例

原创2026-09-21 14:23:241,656 阅读
文章标签:前端静态站点Web框架

Gatsby RSS Feed 生成实战:以 gatsby-plugin-feed 与 Markdown 博客内容为例

导读

本文以 Gatsby 官方仓库中的 feed 示例站点 及其示例文章 posts/2017-03-09-first-post.md 为主线,完整讲解如何基于 gatsby-source-filesystem + gatsby-transformer-remark 把 Markdown 文章变成 RSS Feed,并深入 gatsby-plugin-feed 的源码,讲清 serialize、query、output、match、link 等核心配置的作用原理。读完本文,你将能在自己的 Gatsby 站点中从零搭建一套可复用的 RSS 输出流水线。

示例文章的角色定位:RSS 条目的数据源头

examples/feed/posts/2017-03-09-first-post.md 表面看是一篇占位文章(正文为 Lorem ipsum 测试文本),但它的真实价值在于为整个 RSS 示例提供“标准化的 Markdown 数据模型”。其 frontmatter 结构如下:

---
title: First Post
date: "2017-03-09"
draft: false
---

这正是后续 GraphQL 查询要消费的字段集合:title 与 date 会被 gatsby-plugin-feed 默认查询读取并写入 RSS 条目的标题与发布时间。同目录下的 2017-05-22-second-post.md 与之结构完全一致(标题同为 "First Post",日期为 2017-05-22),两篇文章共同验证了「多篇文章如何按日期排序、逐条生成 RSS item」这一核心链路。

需要特别说明:示例文章只是数据样例,gatsby-transformer-remark 并不要求 frontmatter 必须包含 draft 字段。该字段在这里仅为演示站点所使用,是否参与 RSS 过滤完全取决于你在 serialize 或查询中如何编写逻辑。

示例站点的整体架构:数据如何流动

该示例的最小实现由四份文件构成,构成一条完整的数据链路:

文件 职责
gatsby-config.js 声明 siteMetadata 与插件配置,接入文件系统、Markdown 转换与 RSS 生成
posts/*.md Markdown 内容源,提供 frontmatter 元数据与正文
gatsby-node.js 在 onCreateNode 中为每篇 Markdown 文章计算并写入 slug 字段
src/pages/index.js 页面入口,通过 <a href="/rss.xml"> 暴露生成的订阅地址

数据流向可概括为:

Markdown 文件
   → gatsby-source-filesystem(把文件变为 File 节点)
   → gatsby-transformer-remark(把 Markdown 变为 MarkdownRemark 节点)
   → gatsby-node.js 的 onCreateNode(写入 slug 字段)
   → gatsby build 完成后的 onPostBuild(gatsby-plugin-feed 执行 GraphQL 查询并输出 XML)
   → public/rss.xml

其中 gatsby-node.js 对 RSS 链路有一个关键贡献:它基于文件名生成带前后斜杠的 slug(如 /2017-03-09-first-post/),该 slug 随后会被 feed 插件的 serialize 拼接成文章的完整 URL。这是整个示例中唯一需要“自写逻辑”的部分,也直接决定了 RSS 条目里链接的形态。

gatsby-config.js 解析:站点元信息与插件装配

gatsby-config.js 中 siteMetadata 的三项内容 title、description、siteUrl 是 RSS 生成的“元信息底座”:

module.exports = {
  siteMetadata: {
    title: `GatsbyJS RSS`,
    description: `A blog with RSS powered by GatsbyJS.`,
    siteUrl: `https://www.gatsbyjs.com`,
  },
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `posts`,
        path: `${__dirname}/posts`,
      },
    },
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-plugin-feed`,
      options: {},
    },
  ],
}

三个插件各司其职:

  • gatsby-source-filesystem:把 posts 目录下的 .md 文件作为数据源载入;
  • gatsby-transformer-remark:将 Markdown 内容转换为可查询的 MarkdownRemark 节点,解析 frontmatter、生成 excerpt 与 html;
  • gatsby-plugin-feed:在构建结束(onPostBuild)阶段读取 GraphQL 查询结果,输出 RSS XML。

注意此处 gatsby-plugin-feed 的 options 是一个空对象——这说明该插件自带一整套合理的默认配置,零配置也能直接生成默认的 rss.xml。这一点在阅读 internals.js 源码时可以得到印证:插件内置了默认 query、默认 feeds 数组、默认 generator 标签(GatsbyJS)以及默认的 setup 函数,用于把 siteMetadata 与 feed 配置合并成 RSS 的 feedOptions。

从零配置到自定义:feed 的核心配置模型

官方文档 gatsby-plugin-feed README 给出了完整的自定义写法。插件的顶层选项只有两个:

顶层选项 必填 说明
query 否 一条 GraphQL 查询,默认查询 siteMetadata(title、description、siteUrl)作为站点级上下文
feeds 是 要生成的 RSS 流数组,每个 feed 是一个对象

每个 feed 对象中的必填项为:

feed 键 类型 说明
title string RSS 流的标题
output string XML 文件的输出路径(相对 public/)
query string 获取 RSS 条目内容的 GraphQL 查询
serialize function 接收查询结果,返回符合 rss 包 itemOptions 的对象数组

典型自定义配置(来自 README 示例)如下:

module.exports = {
  siteMetadata: {
    title: `Your site title`,
    description: `Your site description`,
    site_url: `https://your-site-url.com`,
  },
  plugins: [
    {
      resolve: `gatsby-plugin-feed`,
      options: {
        feeds: [
          {
            serialize: ({ query: { site, allMarkdownRemark } }) => {
              return allMarkdownRemark.nodes.map(node => {
                return Object.assign({}, node.frontmatter, {
                  description: node.excerpt,
                  date: node.frontmatter.date,
                  url: site.siteMetadata.siteUrl + node.fields.slug,
                  guid: site.siteMetadata.siteUrl + node.fields.slug,
                  custom_elements: [{ "content:encoded": node.html }],
                })
              })
            },
            query: `
              {
                allMarkdownRemark(
                  sort: { order: DESC, fields: [frontmatter___date] },
                ) {
                  nodes {
                    excerpt
                    html
                    fields {
                      slug
                    }
                    frontmatter {
                      title
                      date
                    }
                  }
                }
              }
            `,
            output: "/rss.xml",
            title: "Your Site's RSS Feed",
          },
        ],
      },
    },
  ],
}

serialize 是整个配置的核心:它为每篇文章返回一个 RSS item 对象,其中 url/guid 由 siteMetadata.siteUrl 与示例站点 gatsby-node.js 生成的 node.fields.slug 拼接而成,custom_elements 中的 content:encoded 则把 node.html(完整 HTML 正文)注入 RSS 条目——这正是“全文订阅”效果的关键所在。

进阶选项:match 与 link

除必填项外,feed 还支持两个插件专属的可选配置(详见 README):

  • match:控制哪些页面会被插入 feed 引用标签。不配置时,所有页面都会自动加入 feed 的 <link> 引用;配置为字符串(如 "^/blog/")时,该字符串会被编译为正则表达式,只有 pathname 匹配的页面才会被插入引用。
  • link:覆盖由 output 推导出的默认 RSS 链接,适用于接入第三方分发服务(如 FeedBurner)的场景。

所有额外选项都会透传给 rss 包的 feedOptions,因此你可以直接写入 language、custom_namespaces(如扩展媒体命名空间)等标准 RSS 字段:

{
  serialize: () => { /* ... */ },
  query: `/* ... */`,
  output: "/rss.xml",
  title: "Your Site's RSS Feed",
  match: "^/blog/",
  link: "https://feeds.feedburner.com/gatsby/blog",
  custom_namespaces: {
    media: 'http://search.yahoo.com/mrss/',
  },
  language: `en-US`,
}

源码级原理:onPostBuild 阶段发生了什么

gatsby-plugin-feed 的生成时机是生产构建,而非开发模式。README 明确指出:插件只在 production 模式下生成 XML 文件,要本地验证需运行 gatsby build && gatsby serve。

生成逻辑集中在 src/gatsby-node.js 的 onPostBuild 钩子中,其流程可以拆解为四步:

  1. 合并默认选项:options = { ...defaultOptions, ...pluginOptions },将 internals.js 中的默认配置与用户在 gatsby-config 中传入的配置合并;
  2. 执行站点级查询:调用 runQuery(graphql, options.query) 获取 siteMetadata,作为所有 feed 的共享上下文;
  3. 遍历每个 feed:若 feed 自带 query,则执行它并借助 merge({}, baseQuery, result) 把站点级数据与 feed 级数据合并;随后调用 feed.serialize(locals) 得到 item 数组,通过 new RSS(setup(locals)) 创建 RSS 实例并逐条 merged.item(item);
  4. 写出 XML:outputPath = path.join('./public', feed.output),自动创建缺失目录后写入 rssFeed.xml() 字符串。

两个值得注意的实现细节:

  • setup 是默认的 feedOptions 装配函数,它把 siteMetadata 展开后与 feed 自身配置合并,这正是 README 中“把 title、description、siteUrl 直接透传给 rss feedOptions”这一描述的源码实现;
  • 若 serialize 不是合法函数,插件会通过 reporter.warn 输出警告并跳过该 feed 的生成,且 runQuery 在 GraphQL 返回 errors 时会直接抛出异常——这两点共同保证了配置错误能尽早暴露。

配置校验:pluginOptionsSchema

src/plugin-options.js 使用 Gatsby 的 Joi 校验机制对插件选项做运行时校验。feed 级对象的必填项(output、query、title、serialize)以及可选的 match、link 都必须在 schema 内声明,其余字段通过 .unknown(true) 放行以便透传给 rss 包。更关键的是,顶层与 feed 级 query 都经过 .external() 钩子调用 parse(query) 做 GraphQL 语法解析,非法查询会在构建早期抛出明确错误信息("query" must be a valid GraphQL query...)。

内置默认 query 与默认 feed

如果你像示例站点一样使用空 options,插件会启用 internals.js 中的默认行为:

  • 站点级查询自动读取 siteMetadata 的 title、description、siteUrl(含 site_url 别名);
  • 默认 feed 查询 allMarkdownRemark,按 frontmatter.date 降序取前 1000 条,输出到 rss.xml。

这意味着:只要你的站点遵循「Markdown 文章 + siteMetadata 三项元信息」这一约定,就能在零配置下得到一份可用的 RSS。但要注意,默认查询不会为条目拼接 url 等链接字段,因此生产站点通常仍需要自定义 serialize 与 query 来生成带完整 URL 的条目。

构建验证与页面展示

package.json 中的脚本与插件要求完全对应:

{
  "scripts": {
    "develop": "gatsby develop",
    "build": "gatsby build",
    "start": "npm run develop"
  }
}

验证步骤:

  1. npm install(依赖 gatsby、gatsby-plugin-feed、gatsby-source-filesystem、gatsby-transformer-remark、react);
  2. npm run build 触发生产构建,onPostBuild 生成 public/rss.xml;
  3. npm run develop 或 gatsby serve 访问站点。

页面端 src/pages/index.js 给出了最简单的订阅入口呈现方式——直接渲染一个指向 /rss.xml 的普通链接:

import React from "react"

const IndexRoute = () => (
  <div>
    <p> Welcome to the GatsbyJS RSS Demo.</p>
    <p>
      <a href="/rss.xml">Click here</a> to see the generated RSS Feed.
    </p>
  </div>
)

export default IndexRoute

生产实践中,可以在此基础上结合 feed 配置里的 match 选项(如 "^/blog/"),让页面在构建时自动注入 RSS 的 <link rel="alternate" type="application/rss+xml"> 引用,从而让订阅器自动发现你的 feed。

测试与回归保障

仓库为插件提供了完整的测试支撑:src/tests/gatsby-node.js 与对应的快照文件 gatsby-node.js.snap、gatsby-ssr.js.snap。这些测试覆盖了 onPostBuild 的 XML 输出形状以及 SSR 阶段 feed 引用标签的注入逻辑,是理解插件输出格式与行为边界的直接参考:当你自定义 serialize 的返回字段时,可通过比对快照中的 XML 结构来确认 RSS 的合法性。

小结

以 posts/2017-03-09-first-post.md 为代表的示例文章,加上 gatsby-config.js、gatsby-node.js 与页面入口,构成了一条从 Markdown 到 RSS 的完整参考实现。其背后是 gatsby-plugin-feed 在 onPostBuild 阶段执行 GraphQL 查询、合并默认配置、逐条序列化并写出 XML 的稳健机制。若要为你的博客接入 RSS,遵循三步即可:在 siteMetadata 中补齐站点元信息、通过 gatsby-transformer-remark 暴露文章的 title/date/excerpt/html、再用 serialize + query 定义条目与输出路径。若需自定义多 feed、按路径匹配注入订阅引用或对接第三方聚合服务,则进一步使用 match 与 link 选项即可。

登录后查看全文
gatsby