Vue and Nuxt 3 have moved beyond simple single-page apps. With hybrid rendering, you can prerender, server-render, and incrementally regenerate pages from the same codebase. But without a CDN-aware caching strategy, you lose the performance and cost benefits. This guide shows how to deploy Nuxt hybrid rendering to the edge correctly.
Nuxt 3 unifies several rendering modes under one router. A single application can serve a static landing page, a server-rendered dashboard, and an ISR-regenerated blog post. The CDN is what makes this hybrid model scalable. It decides what to cache, for how long, and when to revalidate at the edge, close to your users. Misconfigure it and you either serve stale personalized data or bypass the cache entirely and overload your origin.
Hybrid Rendering in Nuxt 3: Beyond SSR vs SSG
Traditional thinking forced a choice: Single Page App (SPA) for interactivity, Static Site Generation (SSG) for speed, or Server-Side Rendering (SSR) for SEO and dynamic data. Nuxt 3 eliminates that tradeoff with hybrid rendering powered by Nitro and routeRules.
Each route declares its own behavior. Nitro then builds the optimal output: prerendered HTML, a serverless function, or a cached payload with a revalidation timer. The CDN simply respects the headers Nitro generates.
- Universal SSR (default): Rendered on demand per request. Ideal for authenticated or highly dynamic content. Cacheable at the edge with short TTLs if not personalized.
- Static Prerendering (SSG): Rendered at build time to static HTML. Deployed directly to CDN storage. Perfect hit rate, zero origin compute, instant global delivery.
- Incremental Static Regeneration (ISR) & Stale-While-Revalidate (SWR): Cached at the edge and regenerated in the background after a TTL expires. Nuxt calls this
israndswr. You get static speed with dynamic freshness. - Client-Only / SPA: Rendered purely in the browser. Useful for admin panels. Still benefits from CDN caching of JS chunks and assets.
The key insight is granularity. Your / and /about can be prerendered, /blog/** can be ISR with a 3600s TTL, and /account can be SSR with no cache. This per-route control is what makes CDN caching strategies so effective for Vue applications.
Why a CDN Is Essential for Nuxt Performance
Nuxt SSR is fast, but origin rendering still adds 80-300ms of compute plus network latency. A CDN collapses that to 15-40ms by serving from edge memory. More importantly, it protects your origin from traffic spikes.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
Three CDN capabilities matter most for Vue/Nuxt:
1. Edge Compute for SSR
Modern CDNs run Nuxt server functions at the edge using Cloudflare Workers, Netlify Edge, Vercel Edge, or AWS Lambda@Edge. Instead of a single origin in us-east-1, your SSR runs in 300+ PoPs. This reduces Time to First Byte (TTFB) and improves Core Web Vitals, especially Largest Contentful Paint (LCP) and Interaction to Next Paint (INP).
2. Tiered Caching for Payloads
Nuxt 3 splits rendering into HTML and JSON payloads (/_payload.json). A CDN should cache both. When a user navigates client-side, Nuxt fetches only the payload, not the full HTML. Caching payloads at the edge makes subsequent navigations feel instant.
3. Asset Optimization
Vue chunks, images, and fonts should be immutable and cached for a year. Nuxt hashes filenames automatically (/_nuxt/entry.abc123.js). Your CDN should serve these with Cache-Control: public, max-age=31536000, immutable. Pair this with image optimization at the edge for responsive NuxtImg delivery without origin transforms.
Edge-Side Caching Architecture
Edge-side caching means the CDN, not the browser, is the primary cache. The browser may hold a short cache, but the edge holds the authoritative copy and serves it to thousands of users.
Caching Strategies by Rendering Mode
Not every route should be cached equally. Use this matrix to map Nuxt rendering modes to CDN cache directives. The goal is to maximize hit ratio without risking personalized data leakage.
| Route Type | Nuxt routeRule | CDN Strategy | Example Header |
|---|---|---|---|
| Static Marketing SSG | prerender: true |
Immutable at edge, long browser cache | public, max-age=31536000, immutable |
| Blog / Docs ISR | isr: 3600 |
Edge cache 1h, SWR background refresh | public, s-maxage=3600, stale-while-revalidate=86400 |
| Product Listing SWR | swr: 60 |
Edge 60s, serve stale while revalidating | public, s-maxage=60, stale-while-revalidate=600 |
| Authenticated SSR | ssr: true + bypass |
Bypass edge cache, private browser cache | private, no-store or CDN-Cache-Control: no-store |
| Assets STATIC | Auto-hashed by Vite | Year-long immutable | public, max-age=31536000, immutable |
For ISR/SWR routes, Nuxt automatically adds Cache-Control and CDN-Cache-Control separation when deployed to a preset that supports it. This lets you keep s-maxage long at the edge while browsers revalidate more frequently. Always verify your CDN respects CDN-Cache-Control or Surrogate-Control — Cloudflare, Fastly, and Vercel do.
Understanding DNS resolution and Anycast routing also helps here. A fast DNS lookup ensures the user hits the nearest edge PoP before any caching logic even runs.
Configuring Nuxt for CDN Deployment
Configuration lives in nuxt.config.ts. The routeRules object is the single source of truth for hybrid behavior. Nitro translates these rules into headers and prerender manifests at build time.
// nuxt.config.ts - Hybrid rendering with edge caching export default defineNuxtConfig({ nitro: { preset: 'cloudflare-pages' // or 'vercel', 'netlify', 'aws-lambda' }, routeRules: { // Static - prerendered at build, cached forever at edge '/': { prerender: true }, '/about': { prerender: true }, '/contact': { prerender: true }, // ISR - cached 1 hour at edge, regenerated in background '/blog/**': { isr: 3600 }, // SWR - fresh for 60s, serve stale for 10min while revalidating '/products/**': { swr: 60, headers: { 'CDN-Cache-Control': 'public, s-maxage=60, stale-while-revalidate=600' }}, // SSR - dynamic, but cacheable for anonymous users '/api/products/**': { headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' }}, // Private - never cache at edge '/dashboard/**': { ssr: true, headers: { 'Cache-Control': 'private, no-store', 'CDN-Cache-Control': 'no-store' }}, '/api/auth/**': { headers: { 'CDN-Cache-Control': 'no-store' } } }, experimental: { payloadExtraction: true // Enables _payload.json caching } })
For fine-grained control, set headers inside server routes or middleware:
// server/routes/api/products.get.ts export default defineEventHandler(async (event) => { const products = await fetchProducts() // Cache at edge for 60s, allow stale for 5min during revalidation setHeader(event, 'Cache-Control', 'public, max-age=10, s-maxage=60') setHeader(event, 'CDN-Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300') setHeader(event, 'Vary', 'Accept-Encoding') return products })
Key details to get right:
- Payload extraction: Enable
experimental.payloadExtractionso Nuxt generates/_payload.jsonfor each route. Cache these at the edge with the same TTL as HTML. - Vary headers: If you render differently based on
Accept-Languageor auth cookies, addVaryto prevent cache poisoning. Better yet, bypass cache for those routes. - Headers vs. CDN rules: Prefer
routeRulesheaders over manual CDN dashboard rules. Version-controlled headers prevent drift and make rollbacks predictable.
Cache Invalidation, Purging, and Observability
Caching is easy; invalidation is hard. With hybrid rendering you have three invalidation paths:
1. Time-based revalidation (SWR/ISR): The most reliable. No purge needed — the edge serves stale content while Nitro regenerates in the background. Set stale-while-revalidate to cover regeneration time. For a blog, s-maxage=3600, stale-while-revalidate=86400 means users never wait for a rebuild.
2. On-demand purge via API: When content changes immediately (price update, CMS publish), call your CDN purge API. Most providers support surrogate key or URL purging. Tag Nuxt responses with Surrogate-Key or Cache-Tag headers and purge by tag:
// Add cache tags for granular purging setHeader(event, 'Cache-Tag', 'products, product-123, category-shoes') setHeader(event, 'CDN-Cache-Control', 'public, s-maxage=300') // Later, purge via API (example: Cloudflare) // POST /zones/:id/purge_cache { tags: ["product-123"] }
3. Build-time invalidation: Prerendered routes only update on new deployment. Use this for truly static content. For everything else, prefer ISR/SWR.
Finally, observe hit rates. A healthy Nuxt CDN deployment should see 85-95% edge hit ratio for anonymous traffic. Monitor via CDN observability tools and track performance metrics like TTFB, cache status (x-cache: HIT/MISS), and origin offload. If your hit ratio is below 70%, you are likely over-bypassing or missing CDN-Cache-Control headers. If users see stale data too long, shorten s-maxage or implement tag-based purging.
Deployment Checklist
- Choose a Nitro preset matching your CDN (cloudflare-pages, vercel, netlify, aws-amplify)
- Define
routeRulesfor every route pattern, not just defaults - Test headers locally with
curl -Iand at edge withcurl -I -H "CF-Cache-Status: HIT" - Enable payload extraction and cache
/_payload.json - Set immutable caching for
/_nuxt/*assets - Implement purge-by-tag for CMS-driven routes
- Monitor hit ratio and TTFB by route, not just globally
When hybrid rendering and edge caching work together, Nuxt delivers the best of both worlds: the SEO and freshness of SSR with the speed and resilience of static. Your Vue app feels instant because most requests never reach the origin — they are served from memory, a few milliseconds away from the user.
Frequently Asked Questions
Should I use SSR or SSG for my Nuxt site behind a CDN?
Use hybrid rendering. Prerender static marketing pages with SSG for maximum CDN hit rate, use SSR with edge caching for personalized or frequently updated pages, and ISR/SWR for content that can be stale for a few minutes like blogs or product listings. This gives you static speed where possible and dynamic freshness where needed.
How do I cache Nuxt SSR responses at the edge without serving stale user data?
Set CDN-Cache-Control with s-maxage for shared edge caches and keep browser cache short. Use Vary headers or cache keys based on cookies and authorization, and bypass cache entirely for authenticated routes via routeRules or CDN bypass rules. Never cache responses that contain user-specific data with a public directive.
What is the difference between CDN-Cache-Control and Cache-Control in Nuxt?
Cache-Control affects both browser and CDN caches. CDN-Cache-Control (or Surrogate-Control) targets only the edge cache, letting you keep a long s-maxage at the edge while using a short max-age in the browser. This allows fast invalidation for users while maintaining high edge hit rates.
How does Nuxt routeRules work with a CDN?
Nuxt routeRules define per-route rendering and caching behavior at build time. Rules like prerender, isr, swr, and headers generate the correct cache headers and static payloads so the CDN can cache each route optimally without custom edge logic. Nitro translates these rules into the proper output for your deployment preset.