惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cisco Talos Blog
Cisco Talos Blog
T
Threat Research - Cisco Blogs
P
Privacy International News Feed
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
Scott Helme
Scott Helme
人人都是产品经理
人人都是产品经理
G
GRAHAM CLULEY
O
OpenAI News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
PCI Perspectives
PCI Perspectives
GbyAI
GbyAI
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
T
Troy Hunt's Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
腾讯CDC
C
Check Point Blog
Spread Privacy
Spread Privacy
L
LINUX DO - 最新话题
Recent Announcements
Recent Announcements
大猫的无限游戏
大猫的无限游戏
P
Palo Alto Networks Blog
Hacker News: Ask HN
Hacker News: Ask HN
M
MIT News - Artificial intelligence
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Hacker News
The Hacker News
H
Hacker News: Front Page
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
T
Tor Project blog
Martin Fowler
Martin Fowler
博客园 - 叶小钗
罗磊的独立博客
C
Cyber Attacks, Cyber Crime and Cyber Security
H
Heimdal Security Blog
V
Vulnerabilities – Threatpost
Simon Willison's Weblog
Simon Willison's Weblog
Latest news
Latest news
WordPress大学
WordPress大学
G
Google Developers Blog
N
Netflix TechBlog - Medium
S
Security Affairs
S
Secure Thoughts
Know Your Adversary
Know Your Adversary

秋实-Allenyou 的小窝

【教程】使用 Keyd 将 Copilot 键映射为可用的组合键并修复触摸板防误触失效问题 机械革命无界 15X 的 Arch Linux 开荒指南 我在用什么?(2026 版) - 秋实-Allenyou 的小窝 从零开始的 NixOS 体验 - 秋实-Allenyou 的小窝 2025:迷茫中转向 - 秋实-Allenyou 的小窝 我在用什么?(2025 版) - 秋实-Allenyou 的小窝 【杂谈】关于 AIGC 的一些思考 - 秋实-Allenyou 的小窝 【记录】一种在主机通过 Docker 容器名解析容器 IP 的方法 【记录】如何在 Next.js 中优雅的调用 Matomo 统计 从信号与系统角度看 OI/XCPC 中的傅里叶变换运用 - 秋实-Allenyou 的小窝 【杂谈】答《博客作者呀,我想采访你这 9 个问题!》 - 秋实-Allenyou 的小窝 使用 Golang 重写 gh-proxy - 秋实-Allenyou 的小窝 FurryCTF 2024 寒假赛 调试窗口 题解 2024:“我”的大学生活 - 秋实-Allenyou 的小窝 【游记】CCPC 2024 重庆站尾杀记 - 秋实-Allenyou 的小窝 【游记】ICPC 2024 亚洲区域赛南京站打铁记 - 秋实-Allenyou 的小窝 【杂谈】这次 Furrygooo 结束后我的一些体验 - 秋实-Allenyou 的小窝 【教程】使用阿里云 OSS + CDN 加速 Gravatar 【记录】 一次罗技鼠标驱动冲突问题的排查经历 - 秋实-Allenyou 的小窝 【记录】我是如何用 Next.js 重构我的博客的 - 秋实-Allenyou 的小窝 【教程】如何用 Git 快速搭建简单的 CI - 秋实-Allenyou 的小窝 我的DN42 Peering信息 - 秋实-Allenyou 的小窝 【进度】博客重构计划 - 秋实-Allenyou 的小窝 这是一个正经的2023总结 - 秋实-Allenyou 的小窝 【题解】Luogu P9769 [HUSTFC 2023] 简单的加法乘法计算题 2023新年谜题总结 - 秋实-Allenyou 的小窝 【教程】浅谈Linux服务器的一些基础安全技巧 - 秋实-Allenyou 的小窝 Goodbye, 2022~ - 秋实-Allenyou 的小窝 【教程】使用 Docker 开服,优雅地隔离不同版本JDK - 秋实-Allenyou 的小窝 【旧文补档】2020年终总结 & 2021新年计划 - 秋实-Allenyou 的小窝 【旧文补档】写个脚本监控你的VPS SolusVM面板适用 - 秋实-Allenyou 的小窝 【旧文补档】建立自己的私人网盘!在CentOS VPS上安装NextCloud - 秋实-Allenyou 的小窝 【旧文补档】2019,再见!2020,你好! - 秋实-Allenyou 的小窝 【旧文补档】新一代USB来袭,一起捋一捋USB的“前世今生” - 秋实-Allenyou 的小窝 【旧文补档】矩阵学习笔记 - 秋实-Allenyou 的小窝
关于 Next.js SSG 生成 RSS Feed 文件的另一思路
2026-03-20 · via 秋实-Allenyou 的小窝

本文将简要介绍除使用 Next.js Route Handler 添加 RSS Feed 文件外的另一种 Dirty 但实用的思路。

CHANGELOG

  • 2026-03-19: 修复 RSS Feed 文件的生成问题。(上一版本调用 generateRssFeed() 函数时,没有等待 Promise 完成,导致 RSS Feed 文件没有正常生成。)

前言

众所周知,Next.js App Router 并没有像 sitemap.ts 一样提供专门的生成入口函数给 RSS Feed 文件。然而,对于博客等内容分享类的站点来说,这又是一种刚需。

大部分情况下,基于 Next.js 开发的站点都会像这篇文章一样,通过添加一个 /feed 路由,并在里面创建 route.ts,利用 Next.js 的 Route Handler 功能生成 RSS Feed 文件。

一般来说,这种做法才是最符合 Best Practice 的做法,并且在 SSR 站点上,只要在 GET() 函数中构建 Response 对象的时候指定好 Content-Type:application/rss+xml 头就能很好地工作。

但是,我的博客——不管是之前的 Allenyou1126/blog-ng-next 还是现在的 Allenyou1126/aki-ssg,使用的都是 Next.js 的 SSG Mode。这样会导致生成的 /feed 路由被 Nginx 默认作为 text/plain 或者 text/html 输出。

这很不优雅。尽管在 Nginx 配置文件里面手动配置 location ^/feed 规则块手动指定 add_header Content-Type application/rss+xml; 也能达到相同的效果,但能在 Next.js 里实现的功能为什么要扯上 Nginx 呢?

因此,我希望找到另一条思路,生成一个静态的 feed.xml 文件。这样,Nginx 自动识别的 MIME 就会是 text/xml——起码这样,当用户直接打开 RSS Feed 链接时,浏览器能够正确地将其作为 XML 文件渲染,而不是作为纯文本或者 HTML 渲染了。

首先,我照常写了一个函数 generateRssFeed() 来生成 RSS Feed 文件的内容。

export async function generateRssFeed() {
	const feed = new RSS({
		title: config.blog.title,
		description: config.blog.description,
		site_url: `https://${config.blog.hostname}/`,
		feed_url: `https://${config.blog.hostname}/feed.xml`,
		language: "zh-CN",
		custom_elements:
			config.follow === undefined
				? undefined
				: [
						{
							follow_challenge: [
								{ feedId: config.follow.feed_id },
								{ userId: config.follow.user_id },
							],
						},
					],
		generator: "Aki-SSG",
	});
	const cms = await initCMS();
	cms.getPostId().forEach((id) => {
		const post = cms.getPost(id)!;
		feed.item({
			title: post.title,
			description: post.markdown_content.toRssFeed(),
			url: `https://${config.blog.hostname}/post/${id}`,
			date: post.modified_at,
		});
	});
	// 调用 feed.xml() 即可获得 XML 文本格式的 RSS Feed 内容
}

RSS Feed 文件位置

有了内容,我们就可以考虑将 RSS Feed 文件整出来了。在 Next.js 没有像 sitemap.ts 一样开洞的情况下,我们要指定某个路由的生成结果只有三种途径:

  • App Router 页面 page.tsx
  • Route Handler route.ts
  • 生成对应的文件,塞到 public 目录下面,然后通过 Next.js 会自动将 public 目录下文件原样复制到站点根目录下的机制将它打包进去

显然,第一条只能生成 HTML 页面,首先出局。第二条就是我们开头说过的思路,排除。那我们就只剩下第三条路径了。

generateRssFeed() 函数的末尾加上写入 RSS Feed 内容到 public/feed.xml 相关代码,现在这个函数长这样:

export async function generateRssFeed() {
	const feed = new RSS({
		title: config.blog.title,
		description: config.blog.description,
		site_url: `https://${config.blog.hostname}/`,
		feed_url: `https://${config.blog.hostname}/feed.xml`,
		language: "zh-CN",
		custom_elements:
			config.follow === undefined
				? undefined
				: [
						{
							follow_challenge: [
								{ feedId: config.follow.feed_id },
								{ userId: config.follow.user_id },
							],
						},
					],
		generator: "Aki-SSG",
	});
	const cms = await initCMS();
	cms.getPostId().forEach((id) => {
		const post = cms.getPost(id)!;
		feed.item({
			title: post.title,
			description: post.markdown_content.toRssFeed(),
			url: `https://${config.blog.hostname}/post/${id}`,
			date: post.modified_at,
		});
	});
	await fs.promises.writeFile(
		path.join(process.cwd(), "public", "feed.xml"),
		feed.xml(),
		{
			flag: "w",
		},
	);
}

看起来很完美,不是吗?

顺带一提,这个函数可以在 Allenyou1126/aki-ssg 仓库的 src/utils/generateRssFeed.ts 找到。

什么时候调用?

现在我们有了一个能生成 feed.xml 的函数了,剩下要做的就是找个合适的时间调用它。

很遗憾,Next.js 并没有提供类似 postbuild 的 Hook。那么我们只能自己想办法。

显然,这个函数应该且必须被调用一次,而且只能在 pnpm build 时调用,不应该被带到 Client 中。

那么我们就不能将他放在页面渲染逻辑中了,剩下的必定会被,且只会在服务器端被执行一次的逻辑,就只有 robots.txt/sitemap.xml 的生成逻辑了。

最后我选择将它塞到了 sitemap.ts 文件中的 sitemap() 函数里调用,看起来长这样:

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
	// ...
	// 这里是 sitemap() 函数的其他逻辑

	// 由于Next没有提供合适的Hook,所以在这里生成RSS
	await generateRssFeed();

	// 这里是 sitemap() 函数的其他逻辑
	// ...
}

你可以在 Allenyou1126/aki-ssg 仓库的 src/app/sitemap.ts 找到这个函数。

现在,运行 pnpm build,在 out 文件夹里面我们就能看到 feed.xml 了。

后记

我知道,这个思路其实有点 Dirty,而且也不符合 Best Practice。但是这确实更符合我的需求——起码可以少配一条 Nginx 配置规则了不是(逃)。