学完这篇你能拿到一套可落地的判断标准:知道自己的 Nuxt 项目在什么条件下该开 SSR、什么条件下开了反而添乱,以及开关和调优的具体改法。
第一步:先弄清 SSR 到底解决了什么
SSR 只做一件事:把「浏览器跑 JS 渲染出页面」提前到「服务器渲染好 HTML 再返回」。它换来三个东西——HTML 里直接有内容、首屏不用等 JS 下载执行、爬虫拿到的是完整页面。
代价同样明确:多了一台常驻的 Node 服务、每次请求都要在服务端跑一遍组件逻辑、多了一层 hydration(注水)问题。
所以判断标准很简单:**你的页面内容是否需要被「不看 JS 的东西」看到,或者首屏内容是否依赖接口数据。** 两个都否,就不需要 SSR。
第二步:对号入座
适合开 SSR 的:
- 内容型页面:博客、文档、论坛帖子页、商品详情、新闻
- 有 SEO 或社交分享卡片需求(微信/微博/FB 抓不到 SPA 内容)
- 希望被 AI 爬虫抓取收录的公开页面
- 用户网络差、设备差,等不起 JS 白屏
不适合的:
- 后台管理系统、登录后的仪表盘(没有爬虫,没有首屏 SEO 诉求)
- 强交互应用:在线编辑器、地图、画板
- 内容固定不变的页面——用 SSG 预渲染更划算,别用 SSR
- 纯内网工具,访问量小且都是自己人
第三步:Nuxt 里怎么开关
入口在 `nuxt.config.ts`:
export default defineNuxtConfig({
ssr: true, // false 则全站退回 SPA 模式
})
一个项目只能选一个全局默认值。默认建议开着,把不需要的页面单独关掉。
注意:`ssr: false` 不是「优化」,它只是关掉服务端渲染。关掉之后就没有任何服务端 HTML,SEO 和首屏优势一起消失。
第四步:更实用的做法是混合渲染
Nuxt 的 `routeRules` 可以按路由决定渲染方式,不用全站一刀切:
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // 首页预渲染成静态
'/blog/**': { swr: 3600 }, // 缓存 1 小时,过期后台重生成
'/dashboard/**':{ ssr: false }, // 后台走纯客户端渲染
'/api/**': { cors: true },
},
})
内容页用 `swr` 或 `isr` 缓存,能挡掉绝大部分服务端渲染压力;登录后的功能页 `ssr: false`,省掉一堆 `window` 不存在的报错。
第五步:数据获取写对,SSR 才有意义
在 `<script setup>` 顶层直接 `fetch` 或写 `onMounted` 里请求数据,服务端这次渲染拿不到数据,出来的还是空 HTML。正确写法:
<script setup>
const { data } = await useFetch('/api/post/1')
</script>
`useFetch` / `useAsyncData` 会把服务端取到的数据序列化进 payload,客户端 hydration 时直接复用,不会重复请求。
注意:不要在 SSR 阶段调用必须带用户 token 的接口。服务端没有浏览器的 cookie 上下文时,这类请求要么失败,要么把别人的数据渲染进 HTML——很危险。这类页面直接 `ssr: false`。
第六步:躲开 hydration 不一致
最常见的报错是 `Hydration node mismatch`,原因基本都是「服务端渲染的结果和客户端第一次渲染不一样」:
- 用了 `Date.now()`、`Math.random()`、`new Date()` 直接渲染
- 用 `window.innerWidth` 判断设备
- 用 `localStorage` 初始化状态
改法:与时间/随机相关的放到 `onMounted` 里;必须客户端才渲染的包一层 `<ClientOnly>`;浏览器判断用 `import.meta.client`。
<ClientOnly>
<HeavyChart />
</ClientOnly>
第三方组件不兼容 SSR 时,也可以在 `nuxt.config.ts` 里给它 `ssr: false` 或改成动态 import。
第七步:部署方式跟着变
开了 SSR 就必须跑 Node 常驻进程,不能只把静态文件丢给 Nginx:
npm run build
node .output/server/index.mjs
用 PM2 守护,Nginx 反代到该端口。要部署到 Vercel / Cloudflare Pages,设置环境变量 `NITRO_PRESET` 对应平台即可。
注意:别在模块顶层放可变全局状态(比如 `let cache = []`)。SSR 下模块在请求间共享,一个用户的数据可能漏给另一个用户。要存状态用 `useState`,它按请求隔离。
小结
- SSR 换的是「HTML 里直接有内容」,代价是常驻服务与 hydration 复杂度,不是无脑优化项。
- 内容型、要 SEO、要被抓取的页面值得上;后台、强交互、纯内网页面不值得。
- 别全站一刀切,用 `routeRules` 做混合渲染,内容页加缓存、功能页关 SSR。
- 数据必须走 `useFetch` / `useAsyncData`,否则服务端渲染出来还是空壳。
- 时间、随机数、`window`、`localStorage` 是 hydration 报错的四大来源。
- 开了 SSR 就得有 Node 进程,顺带管住跨请求的全局可变状态。