Nuxt: SSR 与 CSR 渲染模式
最后更新:2026-08-26
Charlie 发现 MegaShop 所有页面都用 SSR,服务器压力巨大——百万级商品页每次请求都渲染。首页内容不常变,却每次都重新渲染。管理后台不需要 SEO,却在服务端渲染。混合渲染让每个页面选择最优策略:首页 SSG、商品列表 ISR、详情页 SSR、用户中心 CSR。
1. 你将学到
- routeRules 渲染模式配置:ssr/csr/swr/prerender/hydrate
- ISR 增量静态再生:swr 缓存与按需重新验证
- ESR 边缘渲染:Nitro CDN 边缘计算
- 混合渲染实战:不同页面选择不同策略
- MegaShop 百万级商品页 ISR 缓存设计
2. 一个架构师的真实故事
(1) 痛点:全站 SSR 压垮服务器
MegaShop 每秒 5 thousand 请求,全部走 SSR。首页内容一天才变一次,却每次请求都渲染。商品列表每小时更新,百万商品详情页重复渲染。服务器 CPU 跑到 90%,Bob 准备加 10 台服务器。
(2) 混合渲染的解法
Nuxt 3 的 routeRules 让每条路由有独立渲染策略:
TYPESCRIPT
// nuxt.config.ts
routeRules: {
'/': { prerender: true }, // Homepage: build once
'/products': { swr: 3600 }, // Product list: cache 1 hour
'/products/**': { swr: 86400 }, // Product detail: cache 1 day
'/admin/**': { ssr: false } // Admin: client only
}
(3) 收益:服务器负载降低 85%
混合渲染后,SSR 请求从 5 thousand/s 降到 750/s,服务器从 10 台减到 3 台,成本节省 70%。
3. routeRules 渲染模式
(1) 混合渲染决策树
flowchart TB
A[Page Request] --> B{Needs SEO?}
B -->|No| C[CSR - ssr: false]
B -->|Yes| D{Content changes?}
D -->|Never| E[SSG - prerender: true]
D -->|Rarely| F[ISR - swr: seconds]
D -->|Frequently| G[SSR - default mode]
D -->|Varies by region| H[ESR - edge rendering]
C --> I[Admin / Dashboard / User Center]
E --> J[About / Legal / Landing]
F --> K[Product List / Category Pages]
G --> L[Product Detail / Search Results]
H --> M[Global Homepage / Regional Content]
(2) routeRules 选项速查
| 选项 | 值 | 效果 | 适用场景 |
|---|---|---|---|
| ssr | boolean | 开关 SSR | false = CSR |
| csr | boolean | 开关 CSR | 遗留选项 |
| swr | number/string | ISR 缓存秒数 | 定时更新的页面 |
| prerender | boolean | 构建时预渲染 | 静态页面 |
| hydrate | boolean | 只水合不渲染 | 特殊优化 |
| redirect | string | 301/302 重定向 | URL 迁移 |
| cors | boolean | 跨域 | API 路由 |
| headers | object | 自定义响应头 | 缓存/安全 |
▶ 示例:完整的 routeRules 配置
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Homepage: pre-render at build time
'/': { prerender: true },
// Static pages
'/about': { prerender: true },
'/privacy': { prerender: true },
// Product list: ISR with 1 hour cache
'/products': { swr: 3600 },
// Product detail: ISR with 24 hour cache
'/products/**': { swr: 86400 },
// Categories: ISR with 6 hour cache
'/categories/**': { swr: 21600 },
// Admin: client-side rendering only
'/admin/**': { ssr: false },
// User pages: client-side
'/profile/**': { ssr: false },
// API: CORS headers
'/api/**': { cors: true },
// Redirects
'/shop/**': { redirect: '/products/**', redirectCode: 301 },
// Custom headers for static assets
'/_nuxt/**': { headers: { 'cache-control': 'max-age=31536000' } }
}
})
输出:
TEXT
📖 仅展示
// 执行成功
4. ISR 增量静态再生
(1) ISR 工作流程
sequenceDiagram
participant U as User
participant C as CDN/Cache
participant S as Nuxt Server
participant D as Database
U->>C: GET /products/123
C->>C: Cache HIT? (within swr period)
C-->>U: Cached HTML (instant)
Note over C,S: After swr period expires
U->>C: GET /products/123
C->>C: Cache STALE
C-->>U: Stale HTML (still fast)
C->>S: Background revalidation
S->>D: Fetch latest data
D-->>S: Updated data
S-->>C: Fresh HTML
Note over C: Next request gets fresh HTML
(2) ISR vs SSR vs SSG 对比
| 维度 | SSR | SSG (prerender) | ISR (swr) |
|---|---|---|---|
| 渲染时机 | 每次请求 | 构建时 | 缓存过期后后台刷新 |
| 响应速度 | 🟡 中 | 🟢 最快 | 🟢 快(命中缓存) |
| 数据新鲜度 | ✅ 最新 | ❌ 构建时固定 | ⚠️ 最大延迟 = swr 秒数 |
| 服务器压力 | 🔴 高 | 🟢 最低 | 🟢 低 |
| 适用场景 | 实时数据 | 永不变化 | 定时更新 |
▶ 示例:ISR 商品列表页
TYPESCRIPT
// nuxt.config.ts - ISR configuration
export default defineNuxtConfig({
routeRules: {
// Product list: serve from cache, revalidate every 60 seconds
'/products': { swr: 60 },
// Product detail: cache for 1 hour
'/products/**': { swr: 3600 }
}
})
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:程序化 ISR 缓存失效
TYPESCRIPT
// server/api/invalidate-cache.post.ts
export default defineEventHandler(async (event) => {
const { path } = await readBody(event)
// Purge specific cached route
await useStorage('cache').removeItem(`nitro:routes:${path}`)
return { message: `Cache invalidated for ${path}`, timestamp: Date.now() }
})
输出:
TEXT
📖 仅展示
// 执行成功
5. ESR 边缘渲染
(1) 边缘渲染原理
ESR(Edge Side Rendering)在 CDN 边缘节点执行渲染,用户从最近的节点获取 HTML。
| 维度 | 中心 SSR | ESR |
|---|---|---|
| 服务器位置 | 单区域 | 全球边缘节点 |
| 延迟 | 取决于距离 | 最低(就近访问) |
| 适用场景 | 单区域用户 | 全球用户 |
| Nitro 预设 | node-server | cloudflare-pages / vercel-edge |
▶ 示例:Vercel Edge 部署配置
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'vercel-edge'
},
routeRules: {
// Homepage rendered at edge for global users
'/': { swr: 3600 },
'/products/**': { swr: 86400 }
}
})
输出:
TEXT
📖 仅展示
// 执行成功
6. MegaShop 混合渲染策略
(1) MegaShop 页面渲染策略规划
| 页面 | URL | 策略 | swr | 理由 |
|---|---|---|---|---|
| 首页 | / | SSG | - | 内容稳定,构建时渲染 |
| 关于页 | /about | SSG | - | 几乎不变 |
| 商品列表 | /products | ISR | 3600 | 每小时更新商品 |
| 分类页 | /categories/** | ISR | 21600 | 每日更新 |
| 商品详情 | /products/** | ISR | 86400 | 每日更新 |
| 搜索结果 | /search | SSR | - | 实时查询 |
| 购物车 | /cart | CSR | - | 用户专属 |
| 用户中心 | /profile/** | CSR | - | 无需 SEO |
| 管理后台 | /admin/** | CSR | - | 无需 SEO |
| API | /api/** | - | - | 纯数据接口 |
▶ 示例:MegaShop 完整 routeRules
TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
ssr: true,
routeRules: {
// SSG: Pre-render at build time
'/': { prerender: true },
'/about': { prerender: true },
'/privacy': { prerender: true },
'/contact': { prerender: true },
// ISR: Cache with revalidation
'/products': { swr: 3600 }, // 1 hour
'/categories/**': { swr: 21600 }, // 6 hours
'/products/**': { swr: 86400 }, // 24 hours
// SSR: Real-time rendering
'/search': { swr: 0 },
// CSR: Client-side only
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
'/profile/**': { ssr: false },
'/admin/**': { ssr: false },
// API
'/api/**': { cors: true },
// Static assets: long cache
'/_nuxt/**': { headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
'/images/**': { headers: { 'cache-control': 'public, max-age=86400' } }
}
})
输出:
TEXT
📖 仅展示
// 执行成功
(2) 百万商品页 ISR 缓存失效方案
| 事件 | 失效策略 | 实现方式 |
|---|---|---|
| 商品价格更新 | 失效对应商品页 | useStorage('cache').removeItem() |
| 新商品上架 | 失效商品列表 | 批量失效 /products 缓存 |
| 分类变更 | 失效分类页 | 失效 /categories/xxx |
| 全站促销 | 失效所有商品页 | 清空整个 cache storage |
| 库存变化 | 失效对应商品 | Webhook 触发 |
7. 综合示例:MegaShop 渲染模式验证
VUE
<!-- pages/admin/dashboard.vue - CSR mode -->
<template>
<div>
<h1>Admin Dashboard</h1>
<p>Rendering mode: Client-Side Only</p>
<p>Current time: {{ currentTime }}</p>
</div>
</template>
<script setup lang="ts">
// CSR: this page is NOT server-rendered
definePageMeta({ ssr: false, layout: 'sidebar' })
const currentTime = ref(new Date().toISOString())
onMounted(() => {
setInterval(() => {
currentTime.value = new Date().toISOString()
}, 1000)
})
</script>
VUE
<!-- pages/products/[id].vue - ISR mode -->
<template>
<div v-if="product">
<h1>{{ product.name }}</h1>
<p>${{ product.price }} USD</p>
<p>Cache: revalidated every 24 hours</p>
</div>
</template>
<script setup lang="ts">
// ISR: cached for 24 hours via routeRules swr: 86400
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
</script>
❓ 常见问题
Q swr 的数值单位是什么?
A 秒。
swr: 60 表示缓存 60 秒,过期后下次请求触发后台刷新,当前请求仍返回旧缓存。Q prerender 和 swr 能同时用吗?
A 可以。
{ prerender: true, swr: 3600 } 表示构建时预渲染,之后用 ISR 缓存策略刷新。Q ISR 缓存存在哪里?
A 取决于 Nitro 预设。Node.js 预设存在内存/文件系统,Vercel 预设用 Vercel KV,Cloudflare 用 KV 存储。
Q CSR 页面在 SSR 期间返回什么?
A 返回一个空壳 HTML(只有
<div id="__nuxt"></div> 和 JS bundle),所有内容在客户端渲染。Q 怎么验证当前页面用了什么渲染模式?
A 查看源代码(Ctrl+U)——有内容 = SSR/SSG/ISR,空壳 = CSR。响应头
x-nuxt-render-mode 也标识渲染模式。Q 百万商品页 ISR 缓存会不会占满存储?
A 热门商品自然常驻缓存,冷门商品过期后自动淘汰。不需要预缓存所有百万页,只缓存被访问过的。
📖 小节
- routeRules 为每条路由配置独立渲染策略:SSR/CSR/SSG/ISR/ESR
- ISR(swr)是最优平衡:缓存命中快如 SSG,过期后自动刷新保证数据新鲜
- ESR 在 CDN 边缘渲染,全球用户获得最低延迟
- MegaShop 混合策略:首页 SSG + 商品 ISR + 搜索 SSR + 管理 CSR
- 百万商品页 ISR 按访问缓存 + 事件驱动失效,不预缓存所有页面
📝 作业
- 基础题(难度⭐):配置 routeRules,让首页预渲染、管理页 CSR,验证两种模式的"查看源代码"差异
- 进阶题(难度⭐⭐):给商品列表配置 ISR(swr: 60),观察 60 秒后刷新页面数据是否更新
- 挑战题(难度⭐⭐⭐):实现 ISR 缓存失效 API,当商品数据更新时调用 /api/invalidate-cache 失效对应缓存,验证下次请求获取新数据
---|