RSS 输出
本教程共 56 篇 · 第 48 篇 · 更新于 2026-08-07 · 约 10 分钟阅读
本节目标:学会用官方集成 @astrojs/rss 给你的内容站生成一个 RSS 订阅源,让读者用阅读器订阅更新,并掌握从内容集合批量生成条目的做法。
如果你做的是博客、资讯类网站,读者不一定天天来刷。RSS 就是一种「读者订阅后,你发新文他自动收到」的机制。这一章讲 Astro 怎么生成 RSS feed(订阅源)。
RSS 是什么
RSS 是一份固定格式的 XML 文件,里面列出你网站近期的文章标题、链接、摘要和发布时间。读者把这份文件的地址(比如 yoursite.com/rss.xml)放进 RSS 阅读器,阅读器就会定期来取,有新内容就提醒读者。
对内容站来说,RSS 是低成本留住核心读者的好办法。Astro 提供了官方集成 @astrojs/rss 来自动生成它,不用你手写 XML。
安装 @astrojs/rss
用你习惯的包管理器装这个包:
npm install @astrojs/rss
装好后,不需要在 astro.config.mjs 里注册什么集成——它是通过「端点」来工作的(端点我们在第 34 章讲过,就是一个返回响应的特殊文件)。
创建一个 RSS 端点
在 src/pages/ 目录下新建一个文件,名字随意,但扩展名要用 .xml.js(或 .xml.ts),因为它决定输出 URL 是 /rss.xml。常见名字是 rss.xml.js 或 feed.xml.js。
下面是一个最小可用版本:
// src/pages/rss.xml.js
import rss from '@astrojs/rss';
export function GET(context) {
return rss({
// 输出 XML 里的 <title>
title: 'Buzz 的博客',
// 输出 XML 里的 <description>
description: '一位谦虚宇航员通往星辰的指南',
// 从端点上下文拿到你在配置里填的 site
site: context.site,
// 条目数组,先空着
items: [],
// 可选:注入自定义 XML,比如语言
customData: `<language>zh-cn</language>`,
});
}
这里有个关键点:site 来自 context.site,也就是你在 astro.config.mjs 里配置的 site 字段(必须填,且以 http:// 或 https:// 开头)。没填 site,RSS 里的链接就会是错的。所以请先确认配置里有 site。
items 字段:订阅源的内容
RSS 真正有用的是 items 数组,每一项对应一篇文章。一个条目常用的字段有:
title:文章标题。description:摘要或节选。link:文章原始链接(URL)。pubDate:发布时间。content:文章完整正文(可选,支持 HTML)。
获取这些条目数据有两种主流方式:从内容集合取,或从页面文件用 glob 取。
配合内容集合生成条目
如果你用内容集合(第 20、21 章讲过,v5+ 用 src/content.config.ts + 加载器)管理文章,那 RSS 几乎是顺手的事。用 getCollection() 取出集合,再 map 成 RSS 条目:
// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const blog = await getCollection('blog');
return rss({
title: 'Buzz 的博客',
description: '一位谦虚宇航员通往星辰的指南',
site: context.site,
items: blog.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
// 用文章的 id 拼出链接,假设路由是 /blog/[id]
link: `/blog/${post.id}/`,
})),
});
}
注意 link 需要你自己拼。上面假设每篇文章渲染成 /blog/<id>/ 路由,你要按自己实际的路由规则来写。
为了省心,官方还提供一个 rssSchema,可以直接套在内容集合的 schema 上,强制每篇都含 RSS 需要的字段:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { rssSchema } from '@astrojs/rss';
const blog = defineCollection({
schema: rssSchema,
});
export const collections = { blog };
用了 rssSchema,你的文章 frontmatter 就得带上 title、description、pubDate 等,少填会在构建时报错,反而帮你提前发现问题。
用 glob 导入页面文件
如果你的文章是 src/pages/ 下的 .md/.mdx 文件(没用内容集合),可以用 pagesGlobToRssItems() 辅助函数:
// src/pages/rss.xml.js
import rss, { pagesGlobToRssItems } from '@astrojs/rss';
export async function GET(context) {
return rss({
title: 'Buzz 的博客',
description: '一位谦虚宇航员通往星辰的指南',
site: context.site,
items: await pagesGlobToRssItems(
import.meta.glob('./blog/*.{md,mdx}'),
),
});
}
它接受 import.meta.glob 的结果,自动把每个页面的 frontmatter 转成 RSS 条目。比手写 map 省事,但前提是你的 md 文件 frontmatter 里有合规的 title、description 等。
输出完整正文
默认 RSS 只放摘要。想让读者在阅读器里直接看全文,给条目加 content 字段,值是文章的 HTML。
用内容集合时,借助 Markdown 解析器(如 markdown-it)渲染正文,再 sanitize(清理)一遍:
// src/pages/rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import sanitizeHtml from 'sanitize-html';
import MarkdownIt from 'markdown-it';
const parser = new MarkdownIt();
export async function GET(context) {
const blog = await getCollection('blog');
return rss({
title: 'Buzz 的博客',
description: '一位谦虚宇航员通往星辰的指南',
site: context.site,
items: blog.map((post) => ({
link: `/blog/${post.id}/`,
content: sanitizeHtml(parser.render(post.body), {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
}),
...post.data,
})),
});
}
这里要小心图片和相对链接:RSS 阅读器里相对路径图片可能显示不出来,最好用完整 URL。MDX 文件不支持这种方式(组件和 JSX 表达式无法被纯 Markdown 解析)。
收尾:让订阅可被自动发现
生成 rss.xml 后,最好在页面 <head> 里加一行,让读者只输入主页就能被阅读器发现:
<link
rel="alternate"
type="application/rss+xml"
title="你的站点标题"
href={new URL("rss.xml", Astro.site)}
/>
另外,RSS 默认生成的链接带尾斜杠。如果你在 astro.config.mjs 里设了 trailingSlash: "never",记得在 rss() 里也设 trailingSlash: false,两边保持一致,否则 feed 里的链接和真实文章地址会对不上。
想给 feed 美化样式,还可以用 stylesheet 选项指向一个 .xsl 文件。这些属于锦上添花,按需要加即可。
多个订阅源:按分类拆 feed
一个站点若有多个栏目(比如「教程」和「随笔」),可以生成多个 RSS 文件。做法就是建多个端点,每个端点只取对应集合:
// src/pages/tutorial-rss.xml.js
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('tutorial');
return rss({
title: '教程订阅',
description: '只收教程更新',
site: context.site,
items: posts.map((post) => ({
title: post.data.title,
pubDate: post.data.pubDate,
description: post.data.description,
link: `/tutorial/${post.id}/`,
})),
});
}
这样读者能只订阅自己关心的栏目。每个端点对应一个独立 URL(如 /tutorial-rss.xml),互不影响。
常见错误排查
写 RSS 时几个高频坑:
- 链接是相对路径或错了:
link必须填能被拼成绝对地址的路径,且site要配好。RSS 阅读器一般要求条目链接是完整 URL。 site没填或格式不对:必须是https://开头。context.site为空时,整个 feed 的链接都会失效。- 条目字段缺
title或description:RSS 标准里两者至少得有一个,否则阅读器可能忽略该条目。用rssSchema能强制补齐。 - 时间格式:
pubDate用标准Date对象,别传字符串,否则不同阅读器解析不一致。 - MDX 无法取正文:
content用 Markdown 解析的方式不支持 MDX(组件、JSX 表达式无法被纯 Markdown 渲染),MDX 文章建议只输出摘要。
RSS 适合什么类型的站点
不是所有站点都需要 RSS。它最适合「持续产出文章、读者希望被动接收更新」的内容站:博客、周刊、文档更新、播客等。如果你的站点是纯展示型营销页、或交互工具类应用,RSS 价值不大,可以不做。
另外,RSS 和 sitemap 是两套不同东西,别混:sitemap 是给搜索引擎爬虫看的「全站地图」,RSS 是给读者阅读器看的「近期更新流」。两者不冲突,内容站常两个都做——sitemap 管收录,RSS 管订阅。
小结
RSS 输出在 Astro 里就是「装包 + 建一个端点 + 用 rss() 组织 items」。数据来源优先用内容集合,最顺、最稳。配好 site、拼对 link、把 feed 接进 <head>,一个可被订阅的内容站就齐活了。