SSR and CSR Rendering Modes
Charlie discovered that all pages on MegaShop use SSR, putting enormous strain on the servers—with millions of product pages being rendered with every request. The content on the homepage rarely changes, yet it is re-rendered with every request. The admin panel doesn't require SEO, yet it is rendered on the server. Hybrid rendering allows each page to choose the optimal strategy: SSG for the homepage, ISR for product lists, SSR for detail pages, and CSR for the user center.
1. What You'll Learn
- routeRules rendering mode configuration: ssr/csr/swr/prerender/hydrate
- ISR (Incremental Static Regeneration): SWR Cache and On-Demand Revalidation
- ESR Edge Rendering: Nitro CDN Edge Computing
- Hands-On Guide to Hybrid Rendering: Choosing Different Strategies for Different Pages
- MegaShop: ISR Caching Design for Product Pages with Millions of Items
2. A True Story of an Architect
(1) Pain Point: Site-wide SSR Overwhelms the Server
MegaShop handles 5,000 requests per second, all via SSR. The content on the homepage changes only once a day, yet it is rendered with every request. The product list is updated hourly, resulting in the repeated rendering of a million product detail pages. The server's CPU usage has reached 90%, so Bob is planning to add 10 more servers.
(2) Solutions for Hybrid Rendering
Nuxt 3's routeRules allow each route to have its own rendering strategy:
// 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) Benefits: 85% reduction in server load
After implementing hybrid rendering, SSR requests dropped from 5,000 per second to 750 per second, the number of servers was reduced from 10 to 3, and costs were cut by 70%.
3. routeRules Rendering Mode
(1) Hybrid Rendering Decision Tree
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) Quick Reference for the routeRules Option
| Option | Value | Effect | Applicable Scenarios |
|---|---|---|---|
| ssr | boolean | SSR switch | false = CSR |
| csr | boolean | CSR switch | Legacy option |
| swr | number/string | ISR cache duration (seconds) | Pages updated periodically |
| prerender | boolean | Pre-render during build | Static page |
| hydrate | boolean | Hydrate only, do not render | Special optimization |
| redirect | string | 301/302 redirect | URL migration |
| cors | boolean | cross-origin | API routing |
| headers | object | custom response headers | caching/security |
(1) ▶ Example: Complete routeRules configuration
// 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' } }
}
})
Output:
// Execution Successful
4. ISR Incremental Static Regeneration
(1) ISR Workflow
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) Comparison of ISR, SSR, and SSG
| Dimension | SSR | SSG (prerender) | ISR (swr) |
|---|---|---|---|
| Rendering Timing | On Every Request | During Build | Background Refresh After Cache Expires |
| Response Time | 🟡 Medium | 🟢 Fastest | 🟢 Fast (Cache Hit) |
| Data Freshness | ✅ Latest | ❌ Fixed at build time | ⚠️ Maximum delay = swr seconds |
| Server Load | 🔴 High | 🟢 Lowest | 🟢 Low |
| Use Cases | Real-time Data | Never Changes | Scheduled Updates |
(1) ▶ Example: ISR Product List Page
// 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 }
}
})
Output:
// Execution Successful
(2) ▶ Example: Programmatic ISR Cache Invalidation
// 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() }
})
Output:
// Execution Successful
5. ESR Edge Rendering
(1) Principles of Edge Rendering
ESR (Edge Side Rendering) performs rendering at CDN edge nodes, and users retrieve the HTML from the nearest node.
| Dimension | Central SSR | ESR |
|---|---|---|
| Server Location | Single Region | Global Edge Nodes |
| Latency | Depends on distance | Minimum (local access) |
| Use Cases | Single-Region Users | Global Users |
| Nitro Presets | node-server | cloudflare-pages / vercel-edge |
(1) ▶ Example: Vercel Edge Deployment Configuration
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'vercel-edge'
},
routeRules: {
// Homepage rendered at edge for global users
'/': { swr: 3600 },
'/products/**': { swr: 86400 }
}
})
Output:
// Execution Successful
6. MegaShop Hybrid Rendering Strategy
(1) Planning the MegaShop Page Rendering Strategy
| Page | URL | Strategy | SWR | Reason |
|---|---|---|---|---|
| Home | / | SSG | - | Stable content, rendered during build |
| About Page | /about | SSG | - | Virtually Unchanged |
| Product List | /products | ISR | 3600 | Products updated hourly |
| Category Page | /categories/** | ISR | 21600 | Updated Daily |
| Product Details | /products/** | ISR | 86400 | Updated Daily |
| Search Results | /search | SSR | - | Real-time Query |
| Shopping Cart | /cart | CSR | - | For Members Only |
| User Center | /profile/** | CSR | - | No SEO required |
| Admin Panel | /admin/** | CSR | - | No SEO required |
| API | /api/** | - | - | Data-only API |
(1) ▶ Example: Complete routeRules for MegaShop
// 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' } }
}
})
Output:
// Execution Successful
(2) Solution for ISR Cache Expiration on Product Pages with Millions of Items
| Event | Expiration Policy | Implementation |
|---|---|---|
| Product Price Update | Inactive Product Page | useStorage('cache').removeItem() |
| New Products | List of Expired Products | Batch Expire /products Cache |
| Category Changes | Inactive Category Pages | Inactive /categories/xxx |
| Sitewide Sale | Clear All Product Pages | Clear Entire Cache |
| Inventory Changes | Expired Products | Webhook Trigger |
7. Comprehensive Example: Verifying MegaShop Rendering Modes
<!-- 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>
<!-- 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>
❓ FAQ
swr: 60 indicates a 60-second cache; after it expires, the next request triggers a background refresh, but the current request still returns the old cached data.{ prerender: true, swr: 3600 } indicates prerendering during the build process, followed by a refresh using the ISR caching strategy.<div id="__nuxt"></div> and the JS bundle); all content is rendered on the client side.x-nuxt-render-mode also indicates the rendering mode.📖 Summary
routeRulesconfigures a separate rendering strategy for each route: SSR/CSR/SSG/ISR/ESR- ISR (swr) offers the optimal balance: cache hits are as fast as SSG, and automatic refresh upon expiration ensures data freshness.
- ESR renders at the CDN edge, providing the lowest latency for users worldwide
- MegaShop Hybrid Strategy: Homepage (SSG) + Products (ISR) + Search (SSR) + Administration (CSR)
- For product pages with millions of items, ISR uses visit-based caching combined with event-driven invalidation; not all pages are pre-cached.
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Configure
routeRulesto enable pre-rendering of the home page and CSR for the admin page, then verify the differences in the "View Source" output between the two modes. - Advanced Exercise (Difficulty ⭐⭐): Configure ISR (swr: 60) for the product list, and after 60 seconds, refresh the page to see if the data has been updated.
- Challenge (Difficulty: ⭐⭐⭐): Implement the ISR cache invalidation API. When product data is updated, call
/api/invalidate-cacheto invalidate the corresponding cache, and verify that the next request retrieves the new data.
---|



