Nuxt: SSR 与 CSR 渲染模式

最后更新:2026-08-26

Charlie 发现 MegaShop 所有页面都用 SSR,服务器压力巨大——百万级商品页每次请求都渲染。首页内容不常变,却每次都重新渲染。管理后台不需要 SEO,却在服务端渲染。混合渲染让每个页面选择最优策略:首页 SSG、商品列表 ISR、详情页 SSR、用户中心 CSR。

1. 你将学到


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) 混合渲染决策树

100%
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 工作流程

100%
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 热门商品自然常驻缓存,冷门商品过期后自动淘汰。不需要预缓存所有百万页,只缓存被访问过的。

📖 小节


📝 作业

  1. 基础题(难度⭐):配置 routeRules,让首页预渲染、管理页 CSR,验证两种模式的"查看源代码"差异
  2. 进阶题(难度⭐⭐):给商品列表配置 ISR(swr: 60),观察 60 秒后刷新页面数据是否更新
  3. 挑战题(难度⭐⭐⭐):实现 ISR 缓存失效 API,当商品数据更新时调用 /api/invalidate-cache 失效对应缓存,验证下次请求获取新数据

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏