ISR with Cache Components prerenders the routes you list in generateStaticParams and serves an App Shell for everything else. Unknown URLs open instantly, then upgrade in the background once their params are known, so you do not pay build time for pages nobody visits.
This article assumes the Cache Components model. Enable it together with partial prefetching so links start the upgrade before the click:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
partialPrefetching: true,
}
export default nextConfigpartialPrefetching makes a prefetched link render with the destination params resolved, so the first real navigation often lands on the upgraded page instead of the shell.
Choosing the subset
generateStaticParams returns the param values worth prerendering at build time. Popular pages ship fully static, while the long tail stays on demand.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const res = await fetch('https://api.example.com/posts')
const posts = await res.json()
return posts.slice(0, 10).map((p: { slug: string }) => ({ slug: p.slug }))
}Only these ten slugs become concrete pages during the build. Every other slug falls back to the shell and upgrades later. In the Pages Router this took getStaticPaths with fallback: true; under Cache Components that fallback behavior is the default, and generateStaticParams only decides which values skip it.
Build time versus runtime
The behavior depends on whether the params were listed.
| URL | Build time | First visit | Later visits |
|---|---|---|---|
| Listed params | Prerendered page | Static page | Static page |
| Unknown params | App Shell | Shell, then streamed content | Upgraded page |
For a listed slug, the visitor gets a fully static page with no server work. For an unlisted slug, the App Shell arrives instantly and the missing parts stream in, then the route upgrades in the background for the next visitor. A prefetch counts as that first visit, so a link that enters the viewport can start the upgrade before the click.
The split is visible in the next build route table, where the listed slugs sit under the dynamic segment row:
└ /blog/[slug]
├ ◐ /blog/[slug]
├ ○ /blog/shipping-faster
└ ○ /blog/type-safe-routesThe ◐ row is the App Shell that serves every unlisted slug. The ○ rows are the concrete pages built from generateStaticParams, prerendered in full because their params and their cached data were both known at build time. If a listed slug still shows ◐, its data was not cached, so only the shell could be built.
What the upgrade produces
The background render tries to push the static boundary as far down the tree as it can. If every data access is cached and all params resolve, the upgrade produces a fully static page. If some part still reads uncached data or cookies, the upgrade produces a cached page that keeps those fallbacks streaming.
Params resolve in route order. A param value that generateStaticParams did not return stays unresolved and blocks deeper params from upgrading in the same pass. The reusable frame itself is the App Shell.
Choosing what to prerender
Not every route deserves build time. Each prerendered page costs build work and storage, so list the pages that benefit most from being ready and leave the rarely visited long tail on demand.
A page visited once between deploys spent its build time for nothing. For controlling whether unlisted params are allowed at all, see generateStaticParams and dynamicParams.
Rune AI
Key Insights
- generateStaticParams picks the subset of routes to prerender.
- Unknown URLs get an App Shell instantly, then upgrade in the background.
- A prefetch counts as the first visit, starting the upgrade before the click.
- The upgrade is static when all data is cached, otherwise it keeps fallbacks.
- This replaces fallback: true from the Pages Router.
Frequently Asked Questions
Do I need partialPrefetching for ISR with Cache Components?
Does every unknown URL get prerendered at build time?
Conclusion
ISR with Cache Components means you prerender the routes that matter and let everything else upgrade on demand. The App Shell gives unknown URLs an instant first paint, and the background upgrade makes the second visit static.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.