我使用 Next.js 开发了一个网站,并且使用了静态导出。在部署到静态服务器之后,我发现首页跳转很慢。
我的网站会在首页 / 时,通过 navigator.language 判断是跳转到 /zh-cn 还是 /en,然后,这俩页面是需要登录才能访问的,所以没有登录的时候,会再次跳转到 /zh-cn/signin?next=/
也就是说,当我在浏览器地址栏输入 / 时,会进行两次跳转:
- 先从
/跳转到/zh-cn - 再从
/zh-cn跳转到/zh-cn/signin?next=%2F(%2F也就是/)
而我发现,这两次跳转居然要 1 秒左右。
在检查了网络后,我发现第一次跳转时,Next.js 会发起一次请求 /zh-cn.txt?_rsc=rxx9e,第二次跳转时会再发起请求 /zh-cn/signin.txt?next=%2F&_rsc=1xsn2,而这两次请求就是导致跳转时间变长的元凶。
尝试缓存 txt 文件
实际上,在静态导出的情况下,无论 _rsc 参数的值是什么,这些路由片段 txt 文件的内容都是一样的;但是,在下一次修改了代码并重新 build 之后,这些 txt 文件的内容可能会发生变化。
于是,我给 Netlify 添加了一条缓存规则:
[[headers]]
# 匹配以 .txt 结尾且包含 _rsc 参数的 URL
for = "/*.txt"
query = {_rsc = ":any"}
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
但没过多久我就发现:对同一个 txt 文件的请求携带的 _rsc 的值是会变的!
会变化的 _rsc
经过测试,我发现:
zh-cn.txt始终是/zh-cn.txt?_rsc=rxx9e- 我本来还在疑惑 Next.js 为嘛不把内容 hash 写进文件名里,但我后来发现,如果 _rsc 不变,那么静态服务器会响应 304,说明 Next.js 是考虑过这个文件的缓存策略的,只是把它当作类似 html 文件的这种缓存策略了
signin.txt会发生变化:- 如果是从
/zh-cn跳转到/zh-cn/signin?next=%2F,是/zh-cn/signin.txt?next=%2F&_rsc=1xsn2 - 如果是从
/zh-cn/logout跳转到/zh-cn/signin,值是/zh-cn/signin.txt?_rsc=kd8vp - zh-cn.txt 始终是 304,而 signin.txt 即使 _rsc 值一样,也基本都是 200,很少出现 304 的情况(后来发现为什么了,见下文)
- 如果是从
我猜测 _rsc 的变化是因为带有 next 这个 search param 导致的,但是在这之前我以为是 URL 里的 zh-CN 的大小写导致的(可能也有关),这里记录一下。
Netlify 中的 URL 大小写问题
我一开始在 URL 中使用的 locale 是大小写混合的,例如 /zh-CN、/zh-CN/signin,但是,我发现当我刷新网页的时候,Netlify 会自动 301 到小写的网址 /zh-cn、/zh-cn/signin,且这一操作无法禁用,见 How to disable “Uppercase URLs are redirected to Lowercase URLs” feature?
然后我将代码、文件名里用了大小写混合的 locale 的地方全都统一改成了小写的 zh-cn 这种形式,又发现我在地址栏输入 /zh-cn 时,网络里还是有 301 到 /zh-cn 的 document 请求。经过确认,发现是因为我之前输入的网址都是 /zh-CN 形式,所以我现在即使输入了小写的 /zh-cn,浏览器地址栏实际上请求的是曾经的历史记录里的 /zh-CN。于是,我又在地址栏的下拉框里删除了所有 /zh-CN 形式的历史记录,浏览器才开始准确请求 /zh-cn。
但是,_rsc 变化的问题依然存在,所以我才意识到可能还有别的原因会引起 _rsc 变化。
验证猜想
为了验证是否真的是 search param 导致的 _rsc 变化,我将 next 参数改为放在了 hash 里,即以前是 /zh-cn/signin?next=%2F,现在改成 zh-cn/signin#next=%2F,然后再试验上面两种跳转情况,结果发现,跟使用 search param 时是一样的行为大致一样,两点不同:
- 请求 signin.txt 时没有带 next 参数了
- 从 /zh-cn/logout 跳转回来时请求 signin.txt 的 _rsc 值改变了
200 与 304
靠 _rsc 来对 txt 进行缓存是不大可能了,我又将目光转到了 txt 文件的 HTTP 响应码上。理论上,同一次 build 的 txt 文件内容是一样的,那么应该总是返回 304 才对。
我研究了一会儿 signin.txt 的响应头,发现里面既有 Netlify CDN 的相关内容,也有 Cloudflare CDN 的相关内容。我突然想到,在 Netlify 里好像看到过,说是 Netlify 本身有 CDN 功能,所以不应该再套别的 CDN,例如 Cloudflare 的 CDN,而我确实在 Cloudflae DNS 里开启了“代理”选项。
我尝试在 Cloudflare DNS 里关闭了代理选项,即变成了“仅 DNS”,然后再刷新网页——所有 txt 都始终返回 304 了,无论 _rsc 值是什么。
到这里,问题其实已经解决了一大半了,而 _rsc 不恒定导致没法根据 _rsc 来设置一年缓存,所以作罢。
结论
- 不要 CDN 套 CDN,会导致互相冲突。
- _rsc 的值跟跳转方向、也跟 search params 有关。
- 从
/跳转到/zh-cn始终是 rxx9e - 从
/zh-cn跳转到/zh-cn/signin,无论 search params 或者 hash 如何,始终是 1xsn2 - 从
/zh-cn/logou跳转到/zh-cn/signin,一开始是 kd8vp,searc 修改 hash 后是 mz3sd,甚至如果给logout随便加个 search param(比如/zh-cn/logout?abc=def)也会导致值变化 - 刷新网页后,依然是这些值,但暂未验证修改了 zh-cn 或 signin 的 page.tsx 后,是否会导致值发生变化。
- 从
- search params 可能会改变 Next.js 对页面的处理,所以如果仅仅是前端需要的参数,那么建议放在 hash 里。
- 之后遇到类似问题时,也可以从这一点出发。
相关链接: