按内容更新频率给页面选渲染模式:SSR / ISR / SWR / CSR 混合实践
【这是一篇写在此项目开发期的文章】 一个博客里,首页、文章详情、归档、关于、搜索、工具箱的内容更新频率完全不同,用一种渲染模式套全站总让人感到别扭。这篇文章用来记录我按"内容多久变一次"给每个路由分档、用 Nuxt 的 routeRules 配出混合方案的过程,以及在自建 node-server 上踩到的几个坑。

按内容更新频率给页面选渲染模式
主要问题:一种渲染模式套全站,怎么配都别扭
我的博客前台是 Nuxt 3,默认就是 SSR。一开始我图省事,想让全站都用同一种策略,结果就发现了一个问题:
- 全站 SSR:每次请求都要在服务端渲染一遍、再打一遍后端。可归档、关于这种页面几天都不变一次,这样纯属白跑。
- 全站 ISR / SWR:首页和文章详情是实时的——新文章、浏览量、评论,一缓存就"卡"在那一刻,读者看到的是旧页面。
所以我所遇到的情况就是:每个页面的内容更新频率不一样,应该给它们不同的模式。
解决方案:按内容更新频率给页面分档
我的判断标准
我给自己定了一个简单的判断方式:先看这个页面的内容多久变一次,再看该部分内容的更新频率是否会很影响用户体验。
- 每次访问都必须是最新 → SSR,不配缓存规则;
- 几分钟内旧一点没关系 → ISR / SWR,给个 TTL;
- 内容基本不变、但依赖后台配置 → 还是 SSR(不能用构建时预渲染);
- 纯交互、不指望被搜索引擎抓 → CSR。
按这个标准把路由过一遍,基本上每个页面的模式就确定下来了。
具体的 routeRules
Nuxt 的 routeRules 正好就是按路由指定渲染 / 缓存策略的地方。最后我配成这样(myblog-vue/myblog-blog/nuxt.config.ts):
routeRules: {
// 欢迎落地页(/): SSR 渲染(静态落地,不缓存)
"/": { ssr: true },
// 主博客(/home): ISR 缓存 60 秒,过期后陈旧重验证
"/home": { isr: 60 },
// 归档页 ISR: 缓存 300 秒
"/archive": { isr: 300 },
// 分类页 ISR
"/category": { isr: 300 },
"/category/**": { isr: 300 },
// 标签页 ISR
"/tag": { isr: 300 },
"/tag/**": { isr: 300 },
// 关于页 SWR: 5 分钟缓存 + 10 分钟陈旧重验证
"/about": { swr: 600 },
// 搜索页不缓存
"/search": { ssr: true },
// 文章详情页 SSR(实时内容)
"/article/**": { ssr: true },
// 工具箱页纯客户端渲染(各页面 definePageMeta 中已设 ssr: false)
"/tools/**": { ssr: false },
// 上传文件代理 → 后端(backendOrigin 是 NUXT_API_BASE 去掉 /api/v1 后的根地址)
"/uploads/**": { proxy: `${backendOrigin}/uploads/**` },
},
下面把每条想清楚。
/ 欢迎落地页:SSR,不做预渲染
这里我纠结过一阵。欢迎页几乎都是静态的——这个页面也就头像、站点名、一句简介、一个"进入博客"按钮,很适合 prerender: true,构建时就直接烤成 HTML。
但这些内容全都来自后台设置:站点名、站点 Logo、博主的头像和简介,都是运行时从后端拉的。要是构建时预渲染,等于把当时那套配置写死在 HTML 里,之后在后台改了名字、换了头像,不重新构建就看不到变化。
所以最后给了 ssr: true:每次请求服务端渲染,拿到的是最新配置,而页面本身很轻,这点开销可以接受,不过以后可以考虑一些新方案。
/home 主站:ISR 60 秒
主站是文章列表,会随发文变化,但也没必要"每来一个人就重算一次"。60 秒的窗口里旧一点完全可以接受,过期后先返回旧页面、后台再重新生成。
其实 / 原来是主站,配的是 isr: 60。这次把首页拆成 / 欢迎页 + /home 主站之后,ISR 就跟着搬到了 /home,/ 改成了 SSR。
归档 / 分类 / 标签:ISR 300 秒
这几个页面的共同点是"内容由文章聚合而来",只有发新文章、改分类标签时才会变。5 分钟粒度足够,配置上就是 isr: 300(分类和标签还要加上 /** 的详情页)。
关于页:SWR 600 秒
关于页基本不变,10 分钟缓存没什么问题,用 swr: 600。
/search:SSR,不配缓存规则
搜索结果和查询词强相关,是最不该被统一缓存的一类页面。我直接 ssr: true、没给它配缓存规则,每次请求都重新渲染,省得去纠结"缓存 key 里要不要带 query"。
/article/**:SSR,不配缓存规则
文章详情有浏览量和评论,这两个都是每次访问都可能变的。一旦被 ISR 缓存,浏览量就停在缓存那一刻,评论也得等缓存过期才更新。所以详情页老老实实 SSR,也没给它加缓存规则。
/tools/**:CSR
工具箱是纯前端交互——格式化、编码、解析这些,内容不依赖 SEO,也不需要服务端渲染。所以整块 ssr: false。
SSR / ISR / SWR / CSR,我自己的理解
配完这一圈后,我把这些概念又理了一遍:
- SSR(服务端渲染):每次请求,服务端把页面渲染成 HTML 返回。内容最新,代价是每次都要跑一遍渲染、打一遍后端。
- ISR(增量静态再生成):页面按 TTL 缓存,过期后先返回旧页面、后台再生成新的。
- SWR(stale-while-revalidate):同样是"先给旧的、后台更新",在 Nuxt 的 routeRules 里和 ISR 用法几乎一样,只是语义上更偏"缓存"。
- CSR(客户端渲染):服务端只发一个壳,内容全在浏览器里渲染。交互方便,但首屏要等 JS,SEO 也吃亏。
Nitro 的文档里 routeRules 现在还标着 experimental,不过 Nuxt 这边用起来已经很顺手了,我暂时没因为这个遇到什么坑。
踩到的几个坑
自建 node-server 上,isr 和 swr 其实是一回事
这是我配完才反应过来的。文档写得很清楚:isr 和 swr 行为相同,区别在于 isr 能额外把响应交给 CDN 缓存——但那是 Netlify / Vercel 这类平台才有的能力。
我是选择 docker 自托管,跑的是 Nitro 默认的 node_server preset,没有那层 CDN。所以对我来说 isr: 300 和 swr: 300 跑起来没什么区别,isr 更像是"语义上表达我想做什么"。以后要是换到支持 CDN 的平台,这层区别会真正体现出来。
这些缓存是内存里的,重启就没了
ISR / SWR 的缓存存在 Nitro 的 storage 里,我这边没配外部存储,默认就是内存。也就是说容器一重启、或者重新部署,缓存全清,第一波访问得重新生成。
对博客这点访问量影响不大,但得记得它不是"持久化缓存",不能拿它当兜底。
同一套规则写了两遍
/tools/** 我在 nuxt.config.ts 的 routeRules 里写了 ssr: false,工具箱的每个页面里又各自写了 definePageMeta({ ssr: false })。这其实是重复的,留一处就行,我还没清理,先在这里记一笔。
routeRules 不只是"渲染模式"
顺带提一句:这块配置里我还放了 /uploads/** 的代理规则,把上传目录转发到后端。它跟渲染模式没关系,但确实写在同一个 routeRules 里——说明它本质上是"按路由改行为",渲染只是其中一类。
还没想清楚的地方
- 工具箱页是纯 CSR,首屏要等 JS 下载执行,会先白一下。虽然加载模板能缓解白屏,但我不太喜欢那种过渡,先记着吧。
另外 ISR / SWR 的缓存现在是内存的,我在想是不是该接个 Redis,让重启之后不用从头再生成一遍。不过按博客这点访问量,现在做可能有点过度设计,先记着,等真的觉得慢再说。



