最近在用 Next.js 的 RSC(React Server Component)时遇到了一个比较隐晦的问题,记录一下踩坑过程和应对方案。
如果你在项目里既使用了 CDN(并且缓存了 html),又使用了middleware
的重定向(middleware 会处理 rsc 请求),又用了 RSC 特性(开启了 prefetch 等情况),如果你发现部分页面 html 变成了一大串“乱码”(其实是 rsc 请求的返回结果),可以仔细看看。
RSC 请求是怎么工作的?
Next.js 在请求 RSC 和 HTML 页面时,路径和方法其实是一样的,唯一的区别在于它会带上一个 _rsc
的参数和一些特定 header。
比如你访问页面 /about
:
- HTML 请求:
GET /about
- RSC 请求:
GET /about?_rsc=<随机字符串>
以及特定的 header
这里的 _rsc
参数是 Next.js 内部用来标识“这是一次 RSC 请求”,header 里也有类似 Next-Router-State-Tree
, rsc
等用于数据请求和分割的内容。
Next.js 为什么要“吞掉”这些参数?
你以为既然客户端发出了带 _rsc
参数的请求,那么在 middleware 下我们理应可以拿到。但实际情况是——拿不到!
原因就在于:
Next.js 觉得这些属于框架内部的数据传递(不想暴露给开发者),所以默认会把这些 RSC 相关的参数和 header 给“吞掉”,即在 middleware 里拿不到。(测试发现当前版本,即使是正常的 html 请求,这些固定的参数也都会被抹去的,所以不要和官方内部使用的名字撞车)

你可能会发现无论用 request.nextUrl.searchParams.get('_rsc')
还是直接获取 header,都看不到 RSC 的直接痕迹。
为什么会出问题?
在大多数普通场景下,这其实无所谓。但是,有三个条件叠加之后问题就来了:
- 前面有 CDN,并且 CDN 的缓存 key 用 URL+参数 规则
- CDN 规则里会缓存了部分页面的 HTML(如静态页面)
- 在 middleware 里做这些页面的重定向(比如国际化/页面迁移等等情况)
此时,RSC 的请求本身是 /about?_rsc=xxxx
,但我们在 middleware 做重定向或者做其它处理时,参数被吃掉,最后变成了 /about
。
这样,CDN 就完全分不清楚,RSC 请求和正常 HTML 请求是一样的!
比如:
- 客户端请求:
/about?_rsc=1&a=1&b=1
- 重定向,经过 middleware 被 Next.js 干掉后,变成
Location /about?a=1&b=1
- 缺少了
_rsc
参数,CDN 如果只根据 url 和 search param 缓存的话就混淆了
这时你就会发现:刷新页面偶尔还能刷出一堆 RSC 的结果,页面展现一团乱麻。
在 Middleware 里还能区分吗?
那我还能不能从 Middleware 里判断这个请求到底是 RSC 还是 HTML?
结论是——很难稳定区分。
- search params 没了(被吞了)
- headers 里 RSC 的标记也没了(被吞了)
- 只剩下
accept
header:- HTML 请求一般是在
accept
头中包含text/html
- RSC 请求则是
accept: */*
- HTML 请求一般是在
看似可用,实际上这个特征又太草率,万一其它请求也发 */*
呢?总觉得不靠谱。
解决方案——利用 Nginx 兜底
目前我的解决办法就是前置 Nginx。在 Nginx 里为 _rsc
这个参数专门映射一个自定义 header,然后转发给 Next.js。
Nginx 配置示例:
1 | # http块内 和server块同级 |
location 块中
1 | location / { |
然后在 nextjs 中给他放回去: (我这里的重定向是用的 next-intl 的 middleware)
1 | const nextIntlMiddleware = createMiddleware(routing); |
这样,在后端中间件里就能通过自定义请求头判断请求来源,不会再意外丢失参数。
总结
Next.js 的 RSC 请求,本质上和 HTML 请求是同一路径,只是带了内部参数用于区分。CDN 缓存规则如果不区分 _rsc
,就会出现缓存混乱,这对生产系统来说是很大的隐患。
最佳实践建议:
- 对静态页面要么彻底关闭 CDN 缓存,要么配置 CDN 支持根据 header 区分请求(需要 CDN 支持)
- 或者在 Nginx 层把 RSC 参数投递到自定义 header,后续逻辑用 header 判断
这也说明了 Next.js 的黑盒问题,很多细节都需要自己去处理,在vercel
部署没问题的,自己自托管可能就会出问题。还是比较蛋疼的。
参考链接
Rsc header and query params always null on middleware #65787