Skip to main content
Next.js·15 min read

Next.js 16 Cache Components and Partial Prerendering in Production (2026)

Next.js 16 folded Partial Prerendering into Cache Components. Data is dynamic by default; you opt into a static shell with use cache, cacheLife, and cacheTag. This production guide covers enabling cacheComponents without breaking the build, wrapping cookies and auth in Suspense, ISR for unlisted params, and how self-hosted VPS deploys differ from Vercel CDN shells.

By Mussawar Hayat

PPR is no longer a flag you flip and forget.

As of Next.js 16, Partial Prerendering lives inside Cache Components. You enable cacheComponents in next.config.ts. Data is dynamic by default. The static shell is whatever you mark with use cache. Uncached reads such as cookies() or an untagged fetch must sit behind a <Suspense> boundary or the build fails instead of silently making the whole route dynamic.

That inversion is the part teams miss. A marketing page that used to prerender can start erroring at build the moment you turn the flag on. This guide is the production adoption path: enable the model, split shell from holes, set freshness, invalidate on writes, and know what still depends on the platform CDN versus a self-hosted Node process.

What you will implement

  • cacheComponents: true (and optionally partialPrefetching: true on 16.3+)
  • Cached shells with use cache, cacheLife, and cacheTag
  • Suspense holes for cookies, session, cart, and live inventory
  • ISR for params you did not list in generateStaticParams
  • A write path that invalidates tags instead of waiting for a timer
  • A self-hosted checklist so you do not assume Vercel ISR semantics on a VPS

1. Enable the model on purpose

Official Next.js and Vercel docs now treat Cache Components as the way you get a static shell plus streamed dynamic holes. Confirm the current flags in the Next.js documentation for your exact minor version before you copy a snippet from an older PPR preview post.

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true, // 16.3+: instant shells + finer Link prefetch
}

export default nextConfig

Turn this on in a branch, not on Friday production. Existing routes that call cookies(), headers(), or an uncached fetch at the page root will fail the build until you wrap those reads.

2. Dynamic by default, cache by declaration

The old mental model was: static unless you touch request data. Cache Components flips it: dynamic unless you opt a segment into the cache.

  • Shell. Layout chrome, product copy that changes on a CMS publish, category grids that can be a few minutes stale.
  • Hole. Price for the signed-in currency, cart count, feature flags, anything that reads the session.

Put the shell in an async Server Component that starts with 'use cache'. Put the hole in a sibling wrapped by <Suspense fallback={...}>. Do not hide the hole by calling cookies() in the same function that you marked cached. That function cannot be cached if it reads the request.

3. A product page that actually prerenders

Use a cached catalog component plus a Suspense-wrapped cart badge that reads cookies. Call cacheTag('catalog') and cacheLife with a documented profile for your Next.js release. Pair catalog writes with revalidateTag('catalog') from an authenticated Server Action or Route Handler.

Do not read cookies() inside the function marked use cache. Request data belongs in the hole.

4. ISR when generateStaticParams is incomplete

Most catalogues cannot prerender every SKU at build. Next.js 16.3 documents ISR with Cache Components plus Partial Prefetching: params you listed prerender fully; params you omitted can still serve an App Shell on the first visit and upgrade in the background.

That is the replacement for Pages Router fallback: true. List the head of the catalogue in generateStaticParams. Accept that the long tail pays one origin render, then lives in cache until the tag or life expires.

Do not list ten thousand params to feel safe. You will inflate build time and still miss new SKUs created after the deploy.

5. Prefetch is now part of the same story

16.3 Partial Prefetching lets a <Link> pull the reusable shell without downloading every personalised hole. Default prefetch that used to mean fetch the whole target RSC payload can now mean fetch the shell.

Audit marketing nav first. A mega-menu that prefetches twenty product pages should prefetch shells, not twenty cart-aware trees. Keep eager prefetch on money paths after you measure payload size.

6. Self-hosted VPS is not Vercel ISR

This site and many client apps run Next.js output: 'standalone' behind Nginx and PM2. Cache Components still splits the render. What you do not automatically get is a global CDN holding the static shell next to every user.

  • Put Nginx or your CDN in front and cache the document HTML only when the response is actually cacheable. Personalised holes that still set cookies on the document will defeat edge cache.
  • Do not assume revalidateTag fans out to six regions. On one VPS it invalidates that process or that shared cache store if you configured one.
  • Warm the shell for the homepage and top landing URLs after deploy. First-request compile plus cold cache is still a first-visit tax.
  • If you need multi-region shells, terminate TLS at a CDN and treat origin as the function that fills holes.

7. Auth, GDPR, and cached HTML

Never put a personal name, email, or medical flag in a use cache segment. Those belong in the Suspense hole. Cached shells are shared. A leaked first name in the shell is a cache-poisoning and privacy incident, not a styling bug.

Session cookies must stay in the hole. Feature flags that depend on the user stay in the hole. Public flags can live in the shell if you tag them and invalidate on change.

8. Invalidation that matches writes

Timers are a backup. Writes are the source of truth.

  1. CMS publish of a case study → revalidateTag('portfolio')
  2. Price update → tag that SKU, not the whole catalogue if your data layer supports it
  3. Blog post deploy → tag blog-index and the slug tag

If two writers can update the same tag, make the Server Action idempotent. Double publish should not 500; it should no-op the second invalidation.

9. Performance budget

Measure TTFB of the document and the time until the hole streams. A fast shell with a 2s hole still feels broken if the fallback is a blank rectangle. Use a fallback that matches layout height so the page does not jump.

Keep cached fetches inside the cached component. A fetch in the page body that is not cached will pull the route out of the model you think you enabled.

10. When not to enable it yet

  • Every route is fully personalised and has no shared shell worth caching.
  • You are mid-migration off Pages Router and still share a getServerSideProps tree.
  • You cannot wrap cookies() this week without a design pass on fallbacks.

Enable per-app when the homepage and product templates have a real static majority. Do not enable it as a default checkbox on a greenfield that is still 90% dashboard.

11. Common mistakes

  • Turning on cacheComponents and leaving cookies() in the page component.
  • Marking a component use cache and then reading headers() inside it.
  • Using a timer-only cacheLife for inventory that changes on every order.
  • Expecting a single VPS to behave like a global ISR network.
  • Prefetching full personalised trees from a site-wide nav.
  • Putting PII in the shell because the fallback looked empty.

12. FAQ

Is experimental PPR still a thing?

On current Next.js 16 docs, PPR is expressed through Cache Components and use cache, not a separate preview flag. Check your minor version notes.

Does this replace ISR?

No. ISR with Cache Components is how unlisted params get a shell on first visit and a cached full page after.

Can I use this with standalone output on a VPS?

Yes for the programming model. Plan your own HTML cache and tag invalidation story; do not copy a Vercel-only runbook.

What should the Suspense fallback be?

Same geometry as the hole. A zero-height fallback causes layout shift when the stream arrives.

Do Client Components break the shell?

Interactive islands can sit in the shell if they do not need request data at render. Hydration still happens. The cache is about the server output, not about deleting JavaScript.

13. Summary

Cache Components is the production name for what used to be sold as Partial Prerendering. Dynamic by default, static where you say so, holes behind Suspense, freshness via cacheLife and cacheTag, ISR for the long tail of params, and a platform-specific cache story if you are not on Vercel’s network.

Key takeaway

If enabling cacheComponents did not force you to draw a line between shared HTML and request HTML, you have not adopted the model. You have only flipped a boolean.


Need a Next.js 16 rendering model that survives production traffic?

I ship App Router apps on Vercel and on self-hosted Nginx / PM2 / Docker stacks. Get in touch or see full-stack engineering services.

Related reading: Next.js 16.3 instant navigations and stop overusing use client.