Incremental Static Regeneration with Cache Components
Learn how to prerender a subset of dynamic routes, serve App Shells for the rest, and upgrade them after the first visit.
Incremental Static Regeneration (ISR) with cacheComponents gives every route an instant first visit, even for URLs that weren't included in the build.
During build, Partial Prerendering splits each render into two parts:
- The App Shell: the generic, reusable part of the page that doesn't depend on URL data
- The rest of the statically renderable content: the param-specific prerenders for the URLs you list in
generateStaticParams
For a visit to a URL whose params were included in generateStaticParams, Next.js serves the fully prerendered page from the cache. For a visit to a URL whose params weren't, Next.js serves the App Shell instantly, then upgrades it in the background with the now-known params. Subsequent visits to that URL get the upgraded result from the cache, skipping the App Shell entirely.
If you have used ISR or fallback: true in the Pages Router, this is the Cache Components equivalent.
Make sure cacheComponents is enabled in your project:
Example
We'll build a product catalog with category layouts and product detail pages using the Recipe API. You can find the resources used in this example here:
Prepare your routes
Use generateStaticParams to define which param values to prerender. When rendering these routes, the param values are known at build time. The prerender process builds a static shell until it hits runtime APIs or uncached data. See Prerendering for more details.
The category layout prerenders two categories:
Notice that CategoryLayout does not await props.params itself. Instead, it passes the params promise to CategoryHeader inside <Suspense>. The await happens inside the boundary, so for unknown categories Next.js can still generate the App Shell.
The product page prerenders one product per category. generateStaticParams receives the parent category param:
Good to know: You can also use loading.tsx files instead of <Suspense> in JSX. loading.tsx puts the boundary at the segment edge, while inline <Suspense> lets you place it anywhere in the tree.
The data helpers use 'use cache' at the module level so all exported functions are cached. This lets their results be included in the static shell:
If your components access runtime APIs like cookies or headers, wrap them in <Suspense>. Their fallback UI is included in the static shell instead.
At build time
When you run next build, Next.js prerenders the layout for each known category (tops, shorts), plus one render where await params suspends, producing the App Shell for [category].
It also prerenders the page for each known product under each category (tee under tops, joggers under shorts), plus one render where await params suspends, producing the App Shell for [product].
These are combined into:
/tops/tee,/shorts/joggers: fully static pages, both params known/tops/[product],/shorts/[product]: category header rendered, product shows fallback/[category]/[product]: both show fallback
At runtime
A visitor navigates to /tops/tee. Both params were prerendered. They get a fully static page.
The first visit to /tops/overshirt. The product overshirt is unknown, but the category tops was prerendered. Next.js serves the App Shell for /tops/[product] with the category header already rendered. The product streams in.
The first visit to /shoes/basketball-shoes. Neither param was prerendered. Next.js serves the generic App Shell for /[category]/[product]. Both the category and the product stream in.
After the first visit, Next.js renders these routes in the background with the now-known params. The next visitor to the same URLs gets the upgraded result.
What the upgrade produces
After the first visit, Next.js renders the page in the background with the known params and tries to push the static boundary as far down the component tree as possible:
- If every data access is cached and all params are resolved, the upgrade produces a fully static page.
- If all params are resolved but the render still hits uncached data or runtime APIs (
cookies,headers) wrapped in<Suspense>boundaries, the upgrade produces a cached page with those fallbacks. The uncached or runtime parts stream in at request time. - Params are resolved in route order. A param value not returned by
generateStaticParamsstays unresolved and prevents any deeper params from upgrading.
Good to know: Prefetching also triggers upgrades. When a <Link> enters the viewport or router.prefetch is called, Next.js can upgrade the App Shell in the background, so the next visitor gets the more specific version even before anyone actually navigates to the page.
Coming from the Pages Router
If you are migrating from the Pages Router:
fallback: trueingetStaticPathsis now the default behavior withcacheComponents. Visitors get a<Suspense>fallback instantly and content streams in.router.isFallbackis not needed. The prerendering step generates a static shell, and you can grow it further with'use cache'.getStaticPropswithrevalidatemaps to'use cache'withcacheLife.getStaticPathsmaps togenerateStaticParams.
Next steps
- Caching with Cache Components for the full caching model
generateStaticParamsfor controlling which param combinations are prerenderedloading.tsxfor providing skeleton UI in App Shells- Streaming to learn how to progressively render UI as data becomes available
- Self-hosting to keep an existing ISR
cacheHandleralongsidecacheHandlersfor'use cache'