Implement SSR caching in Next.js by setting Cache-Control headers with s-maxage and stale-while-revalidate directives in getServerSideProps, enabling CDN caching with background revalidation
Implementing caching for Server-Side Rendering (SSR) in Next.js transforms dynamic pages into performant, cacheable assets. By setting appropriate Cache-Control headers in getServerSideProps, you instruct CDNs to store rendered HTML and serve it instantly to users, while using stale-while-revalidate to keep content fresh in the background. This approach reduces server load, improves Time to First Byte (TTFB), and provides a balance between dynamic data needs and static performance.
public: Allows any cache (CDN, proxy, browser) to store the response. Essential for CDN caching.
s-maxage: Specifies how long the CDN should consider the response fresh (in seconds). After this, it becomes stale.
stale-while-revalidate: During this window, the CDN can serve the stale response while asynchronously fetching a fresh version.
stale-if-error: If the origin fails during revalidation, serve stale content for up to this many seconds as a fallback.
private: Prevents CDN caching; only browser can cache. Use for user-specific content.
no-cache, no-store: Disables caching entirely. Use for sensitive or real-time data.
The stale-while-revalidate pattern is the foundation of modern SSR caching. When a request arrives for a cached page, the CDN checks its freshness: If within s-maxage, the cached version is served instantly (HIT). If past s-maxage but within stale-while-revalidate window, the CDN serves the stale version immediately while triggering a background request to your origin to generate a fresh copy. The next user gets the updated version. This ensures users never wait for page generation, even during cache expiration, while content eventually updates. This pattern is identical to how ISR works, but applied to SSR responses.
When deploying to platforms like Vercel, the Cache-Control headers are automatically respected by their global CDN. You can monitor cache performance using response headers: x-vercel-cache indicates HIT, MISS, or STALE. For self-hosted deployments with CDNs like Cloudflare, Fastly, or AWS CloudFront, these headers are also respected. However, you may need to configure your CDN to forward the Cache-Control headers correctly and set up purging mechanisms for manual invalidation when content changes unexpectedly.
Marketing pages (homepage, about): s-maxage=3600, stale-while-revalidate=86400 (1 hour fresh, 24 hours stale) - content rarely changes
Product pages (e-commerce): s-maxage=300, stale-while-revalidate=3600 (5 minutes fresh, 1 hour stale) - price/inventory may update
Blog posts: s-maxage=3600, stale-while-revalidate=86400 - updates infrequent, but comments can be client-loaded
API endpoints: s-maxage=60, stale-while-revalidate=300 - balance freshness and performance
User dashboards: private, no-cache - never cache user-specific data
Error pages (404, 500): s-maxage=5, stale-while-revalidate=60 - brief cache to prevent stampedes
When you serve different content based on request characteristics (geolocation, device type, language), you need the Vary header. It tells the CDN to cache multiple versions of the same URL, keyed by specific request headers. For example, if you serve different content for mobile vs desktop, use Vary: User-Agent. If content differs by country (using Vercel's geolocation headers), use Vary: X-Vercel-IP-Country. This ensures users get the right version without hitting your origin.
In the App Router, the pattern changes slightly. For API routes, you set headers on the NextResponse object as shown in the example. For page components, you typically use Server Components with Suspense and streaming, but you can still set headers using the headers() function in a layout or page.
Route Handlers (API routes): Use NextResponse.json() or NextResponse.next() with headers option.
Server Components: You can't directly set headers from a Server Component, but you can use middleware to set cache headers based on the route.
Middleware approach: Set Cache-Control headers in middleware for specific paths, which is often cleaner than setting them in every getServerSideProps.
The stale-while-revalidate pattern works the same way regardless of router version.
With aggressive caching, you need a way to invalidate content when it changes. For time-sensitive updates, use on-demand revalidation via webhooks (similar to ISR). For immediate updates, you can implement a purge mechanism with your CDN. Vercel provides the revalidatePath and revalidateTag APIs that work with cached SSR responses. When you call these functions, the CDN cache is purged and the next request triggers a fresh render. This gives you the performance of caching with the flexibility of instant updates when content changes.
We have a Next.js page that fetches product data at request time. How would you add caching so that the same product page is served quickly for subsequent users?
If you set Cache-Control: s-maxage=60, stale-while-revalidate=30 on a server‑side rendered response, what behavior will the CDN exhibit for the first request and for later requests within those windows?
Your team noticed that after deploying a new version, some users still see stale product data for up to a minute. Walk me through how you would debug the caching headers and CDN configuration to fix it.
When implementing ISR alongside SSR for a dashboard, how do you decide which pages get stale-while-revalidate versus a short max-age, and what trade‑offs are you considering?
Design a caching layer for a high‑traffic Next.js e‑commerce site that uses both edge CDN caching and server‑side revalidation. Explain how you would coordinate Cache-Control, revalidate in getServerSideProps, and fallback logic for cache misses.
What edge cases can cause a CDN to serve a 500 error page even though your Next.js server returns a valid response, and how would you mitigate them in your caching strategy?
Our company is moving from a monolithic SSR app to a micro‑frontend architecture with multiple Next.js services behind a shared CDN. How would you define a global caching policy that balances freshness, cost, and cross‑team ownership, and how would you enforce it?
If you need to migrate an existing Next.js site that currently relies on custom server middleware for caching to a fully static‑site generation + CDN approach, what steps would you take to ensure zero downtime and consistent cache invalidation across regions?