Home/Learn/Next.js/Caching at the Edge in Production

Caching at the Edge in Production

Advanced
Production

Beyond the framework's caches sits the CDN, and beyond that the browser. A cache key that ignores the user is the most damaging bug in this whole track.

Overview

The caching concepts earlier covered what Next does inside your application. In production two more layers sit in front — a CDN holding your responses, and each user's browser holding whatever the headers permitted — and they obey `Cache-Control` rather than your `revalidate` export. This is where a caching mistake stops being a staleness annoyance and becomes a security incident, because a personalised response cached under a URL-only key gets served to the next person who asks for that URL.

The Layers in Front

What Next sets by default, and what each layer does with it.

s-maxage for the CDN, max-age for the browser
// browser cache -> CDN -> Next's caches -> your data

// What Next emits by default:
//   a static page       s-maxage long, stale-while-revalidate
//   an ISR page         s-maxage = your revalidate, SWR
//   a dynamic page      no-store — never cached anywhere
//   /_next/static/*     immutable, one year (filenames are hashed)

// s-maxage applies to shared caches (the CDN); max-age applies to
// the browser. The distinction matters:
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400, max-age=0
// -> the CDN caches for an hour and can serve stale for a day while
//    refreshing; the browser always revalidates.
// That combination is usually what you want: purging the CDN then
// fixes a bad page for everyone, rather than waiting out browser
// caches you cannot reach.

// In a route handler you set it yourself:
return NextResponse.json(data, {
  headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' },
})
// A route handler with no header and no revalidate is uncached in
// Next 15 and cached in 14 — be explicit.

Never Cache a Personalised Response

The failure that turns a performance setting into a data breach.

private, no-store, and the user in the key
// A CDN caches by URL. If /dashboard renders one user's data and is
// cacheable, the next person to request /dashboard gets THEIR page.
// This has happened to large companies, and it is entirely avoidable.

// The rules:
//   1. Anything per-user is dynamic and no-store. Next does this
//      automatically once you read cookies() — which is one more
//      reason not to fight it.
export const dynamic = 'force-dynamic'
//   2. If you must cache something user-varying, the user must be in
//      the KEY, not just in the response:
Cache-Control: private, max-age=60          // browser only, never shared
Vary: Cookie                                 // if a shared cache sees it
//   3. Never put a personalised fragment inside a page you also
//      prerender.

// The audit that catches it, on any route that shows user data:
curl -sI https://aicancode.org/dashboard | grep -i cache-control
// Expect no-store. Anything with s-maxage on a personalised route is
// a live incident.

// The same rule applies to your API responses: a FastAPI endpoint
// returning per-user data behind a CDN needs Cache-Control: private,
// no-store, and Vary: Authorization if a shared cache can see it.

Purging and Verifying

Making a change visible, and proving it took effect.

x-vercel-cache and age tell you the truth
// Layers purge differently:
//   Next data / route cache   revalidateTag or revalidatePath
//   Vercel's CDN              follows the above automatically
//   Cloudflare or your own    an explicit purge API call
//   the browser               you cannot. Only a new URL helps —
//                             which is why static assets are hashed.

// So never send a long max-age to the browser for HTML. Keep browser
// caching for hashed assets and let the CDN hold the pages.

// Verify with headers rather than by refreshing and hoping:
curl -sI https://aicancode.org/problems | grep -iE 'cache|age|x-vercel'
//   x-vercel-cache: HIT | MISS | STALE | REVALIDATED
//   age: 320                    seconds since it was cached

// A deploy invalidates Next's own caches; a CDN in front may not
// notice, which is the usual cause of "the new version is live but I
// still see the old page".

// And the standing reminder from earlier in this track: dev does not
// cache. Every conclusion about caching must come from
// npm run build && npm start, or better, from the deployed site.

Key Points to Remember

  • 1A CDN caches by URL, so a cacheable personalised page can be served to the wrong user
  • 2Per-user routes must be no-store; use private with Vary if a response must be cached at all
  • 3Reading cookies() makes a route dynamic automatically, which is a safety feature rather than a limitation
  • 4Browser caches cannot be purged — cache pages at the CDN and reserve long browser caching for hashed assets
  • 5Verify with response headers such as x-vercel-cache and age rather than by refreshing and hoping

Interview Questions

Sign in to ask Aria
1

What is the risk of caching a page that shows user-specific data?

Hard
2

What is the difference between s-maxage and max-age?

Medium
3

Why can you not purge a browser cache, and what follows from that?

Medium

Ask Aria about Caching at the Edge in Production

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…