Deploying Next.js without a CDN-aware strategy is leaving 70% of its performance on the table. Unlike a traditional single-page application, Next.js produces multiple output types that each demand a different caching and compute model at the edge. A static page generated at build time should be cached immutably for a year. An ISR page needs stale-while-revalidate semantics. An Edge Function must execute within 50ms of the user. If your CDN treats them all as generic origin responses, you get cache thrashing, slow TTFB, and unnecessary origin load.
This guide breaks down how to architect your CDN for each Next.js paradigm. We will cover cache-control headers, surrogate keys, revalidation flows, and when to push logic to the edge versus keeping it at the origin. The goal is sub-100ms global TTFB with minimal origin hits and zero stale-data surprises.
1. The Three Rendering Pillars: Where Compute Meets Cache
Every Next.js route resolves to one of four behaviors, and your CDN must distinguish them. Understanding this mapping is foundational to any CDN fundamentals guide for modern frameworks.
Static Site Generation (SSG): app/page.tsx without dynamic data or with generateStaticParams. Rendered once at build time to static HTML and JSON. Ideal for marketing pages, docs, and product listings. The CDN can cache these forever because the asset hash changes on redeploy.
Server-Side Rendering (SSR): Dynamic routes using cookies(), headers(), or searchParams without caching. Rendered per request at the origin. The CDN should typically bypass cache or use very short, private caching unless you add fetch with cache options.
Incremental Static Regeneration (ISR): The hybrid. Static generation with a time-based or on-demand revalidation window. The first request caches the page at the edge; subsequent requests are instant. After revalidate seconds, the next request triggers a background rebuild. This is where most CDN misconfigurations happen.
Edge Runtime: Code that runs on V8 isolates at the CDN edge, not in a Node.js server. Declared via export const runtime = 'edge'. Used for Middleware, Edge API Routes, and edge-rendered pages. It has constraints — no native Node APIs, 1-4MB code limit, 30s CPU limit — but offers single-digit millisecond execution near the user.
2. SSG + CDN: Set It and Forget It (Almost)
SSG is the simplest to cache and the easiest to break. When you run next build, Next.js emits HTML, RSC payloads, and static assets with content hashes. These files never change until the next deployment. Your CDN should treat them as immutable.
| 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 |
The optimal header strategy separates browser cache from CDN cache. Browsers can cache for a year, but the CDN needs deployment-aware invalidation. Use the CDN-Cache-Control draft header to give the edge a different TTL than the browser, a technique detailed in our CDN caching strategies deep dive.
// next.config.js — immutable asset headers for SSG async headers() { return [ { source: "/_next/static/:path*", headers: [ { key: "Cache-Control", value: "public, max-age=31536000, immutable" }, { key: "CDN-Cache-Control", value: "public, max-age=31536000, immutable" } ] }, { source: "/:path*", // HTML pages — let ISR/SSG control via fetch cache headers: [{ key: "X-Content-Type-Options", value: "nosniff" }] } ]; }
On platforms like Vercel, Cloudflare, or Netlify, SSG pages are automatically uploaded to edge storage and served via Anycast. On a custom CDN (CloudFront, Fastly, Akamai), you must configure origin shielding and ensure your build output is synced to edge storage or that your origin returns Cache-Control: public, s-maxage=31536000, stale-while-revalidate for prerendered HTML. Avoid setting no-cache globally — it disables the entire benefit of SSG.
For cache invalidation, SSG requires atomic deploys. Do not purge by URL. Purge by deployment ID or use versioned URLs. Modern CDNs support surrogate keys: tag all assets from deployment d_abc123 and purge that key on rollback. This prevents mixed-version states where HTML references an old JS chunk.
3. ISR: The CDN’s Most Powerful — and Misunderstood — Feature
ISR is stale-while-revalidate at the framework level. It solves the core tension between static performance and dynamic content. Without a CDN that understands ISR, you will see either stale data forever or origin overload.
Here is the precise flow: A user requests /blog/[slug]. The CDN edge checks its cache. On a MISS, it fetches from origin, which renders the page, stores it in the Data Cache, and returns it with Cache-Control: s-maxage=60, stale-while-revalidate=300. The edge caches it. For the next 60 seconds, every request is a HIT. At 61 seconds, the cache is stale. The next request still gets the stale page instantly, but the edge triggers a background revalidation fetch to the origin. Once the origin finishes, the edge swaps the cache atomically. No user waits for regeneration.
// app/blog/[slug]/page.tsx — time-based ISR export const revalidate = 60; // seconds export async function generateStaticParams() { const posts = await fetch("https://api.example.com/posts").then(r => r.json()); return posts.map((p) => ({ slug: p.slug })); } export default async function Page({ params }) { const data = await fetch(`https://api.example.com/posts/${params.slug}`, { next: { tags: [`post-${params.slug}`] } }); return <Article data={data} />; }
Time-based revalidation is good for low-stakes content. For CMS-driven sites, on-demand revalidation is superior. It gives you precise control and is critical for correct CDN cache invalidation. Instead of waiting 60 seconds, your CMS webhook calls revalidateTag('post-hello-world'), which purges both the Next.js Data Cache and the associated CDN cache entries via cache tags.
// app/api/revalidate/route.ts — on-demand ISR import { revalidateTag } from "next/cache"; import { NextRequest, NextResponse } from "next/server"; export async function POST(req: NextRequest) { const { tag, secret } = await req.json(); if (secret !== process.env.REVALIDATE_SECRET) return NextResponse.json({ error: "Invalid" }, { status: 401 }); revalidateTag(tag); return NextResponse.json({ revalidated: true, tag }); }
Key CDN requirement: Your provider must support cache tagging or surrogate keys. Vercel does this natively via x-vercel-cache-tags. On Cloudflare, use Cache-Tag headers. On Fastly, use Surrogate-Key. Without tagging, on-demand ISR degrades to a full purge, which is slow and defeats the purpose. Also ensure your CDN respects stale-while-revalidate and does not collapse concurrent revalidation requests — enable request coalescing.
4. Edge Runtime: Compute at the Last Mile
Edge Runtime moves logic from a centralized origin to 300+ points of presence. The performance gain is not just latency; it is also resilience. An edge function that personalizes content based on geolocation or cookies does not need to round-trip to us-east-1.
Common use cases include: Middleware for authentication and redirects, A/B testing via cookies, geofencing, bot protection, and edge-rendered personalization. The trade-off is runtime limitations. Edge Runtime uses WinterCG APIs — fetch, Request, Response, crypto — but not fs, child_process, or native Node modules. Bundle size matters; keep it under 2MB compressed.
// middleware.ts — runs on every request at the edge import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export const config = { matcher: ["/dashboard/:path*", "/api/edge/:path*"] }; export function middleware(req: NextRequest) { const country = req.geo?.country ?? "US"; const token = req.cookies.get("session")?.value; if (!token) return NextResponse.redirect(new URL("/login", req.url)); const res = NextResponse.next(); res.headers.set("x-geo-country", country); return res; } // app/api/edge/personalize/route.ts export const runtime = "edge"; export async function GET(req: Request) { const city = req.headers.get("x-vercel-ip-city") ?? "unknown"; return Response.json({ city, message: `Hello from ${city}!` }); }
From a CDN perspective, Edge Functions bypass traditional caching. They are not cached unless you explicitly set Cache-Control on their response. For performance, cache edge responses that are deterministic per region or user segment using Vary headers and CDN tiered caching. For example, cache a geolocation API for 60 seconds with Vary: x-geo-country.
Transport matters here. Edge Functions benefit disproportionately from modern protocols. Ensure your CDN negotiates HTTP/3 over QUIC at the edge — it reduces handshake latency and handles packet loss better for mobile users hitting edge compute. Pair this with CDN monitoring and observability to track edge function cold starts, CPU duration, and cache hit ratios separately from origin metrics.
5. Deployment Strategy Matrix
Choosing the right mode per route is an architectural decision. The wrong choice costs either build time, origin cost, or user latency. Use this matrix to map routes to CDN behavior.
| Strategy | Best For | CDN Cache TTL | Revalidation | Origin Load |
|---|---|---|---|---|
| SSG | Marketing, docs, blog index | 1 year (immutable) | On deploy only | Zero after deploy |
| ISR (time) | Product catalog, news feed | 60–3600s + SWR | Background after TTL | Low, bursty |
| ISR (on-demand) | CMS content, pricing | Indefinite + tag purge | Webhook via revalidateTag | Minimal, precise |
| Edge Runtime | Auth, personalization, A/B | 0–60s or Vary-based | Per-request compute | None (edge compute) |
| SSR (Node) | Private dashboards, checkout | Bypass / private | Per request | High |
Recommended hybrid architecture: Deploy SSG and ISR pages to edge cache with tiered caching and origin shield. Deploy Middleware and personalization to Edge Runtime. Keep authenticated SSR routes on Node serverless functions with Cache-Control: private, no-store and protect them via CDN token authentication. This isolates cacheable traffic (90%+) from dynamic traffic and lets you scale each independently.
6. Production Checklist for CDN + Next.js
- Enable Cache Tags: Configure your CDN to forward and respect
Cache-Tag/Surrogate-Keyheaders for granular ISR purges. Test withcurl -Iand verifyx-cache: HITafter revalidation. - Separate CDN-Cache-Control: Use long
s-maxagefor the edge and shortermax-agefor browsers. Browsers should revalidate more often than the edge to avoid stale UI after deploys. - Pre-warm ISR: After deploy, crawl critical ISR paths with a build script to populate edge caches before users arrive. This avoids the first-request MISS penalty.
- Monitor Hit Ratio: Aim for >95% edge hit ratio on SSG/ISR routes. A drop below 90% indicates misconfigured
Varyheaders or cookie leakage into cache keys. - Secure Revalidation: Protect
/api/revalidatewith a secret, IP allowlist, and rate limiting. Log all purges. An exposed endpoint allows cache poisoning. - Compress at Edge: Enable Brotli and Zstandard at the CDN. Next.js payloads (RSC, JSON) compress 70–80% and should be cached compressed.
When done correctly, your origin handles only cache misses and mutations. The CDN serves everything else from memory, 20ms from the user. That is the promise of Next.js on the edge — not just faster pages, but a fundamentally cheaper and more resilient architecture.
Next step: Audit your current Cache-Control headers with curl -I https://yourdomain.com/blog/slug and compare against the matrix above. If you see no-store on a static page or no stale-while-revalidate on ISR, you have an immediate optimization opportunity.
Frequently Asked Questions
How does ISR work with a CDN cache?
ISR stores the statically generated page at the CDN edge. The first request populates the cache. Subsequent requests are served instantly from the edge until revalidate expires. After expiry, the next request triggers background regeneration while serving stale content, then the CDN cache is updated atomically.
Should I use Edge Runtime or Node Runtime for CDN deployment?
Use Edge Runtime for lightweight, latency-sensitive logic like personalization, A/B testing, middleware, and geolocation. Use Node Runtime for heavy computation, large dependencies, or APIs requiring Node.js native modules. Many production apps use a hybrid: Edge for middleware and ISR pages, Node for API routes.
What is the best cache-control strategy for SSG pages on a CDN?
For SSG, use immutable caching with long TTLs: Cache-Control: public, max-age=31536000, immutable and CDN-Cache-Control with s-maxage. Since SSG files are content-hashed and deployed atomically, they can be cached indefinitely at the edge and browser. Purge only on new deployments.
How do I handle on-demand revalidation behind a CDN?
Use Next.js revalidateTag() or revalidatePath() with a secure webhook. When your CMS updates content, call the revalidation endpoint. The Next.js server purges the internal Data Cache and the CDN layer via cache tags. Ensure your CDN supports surrogate keys or cache tagging for precise invalidation without full purge.