ISR

Incremental Static Regeneration

A catalog has more URLs than you want to build. Under Cache Components, ISR is not a separate feature: it falls out of the static shell plus generateStaticParams. You list the params worth prerendering; every other URL is served the App Shell — the same shell with the param-specific parts left behind their fallbacks — and upgraded in the background once the params are known.

Prerender two products, stream the rest (app/products/[id]/page.js)Shell
export async function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }];
}
async function getProduct(id) {
  "use cache";
  cacheLife("days");
  return { id, name: `Product ${id}`, at: new Date().toISOString() };
}
async function Details({ params }) {
  const p = await getProduct((await params).id);
  return <p>{p.name} — built at {p.at}</p>;
}
export default function ProductPage(props) {
  return <main><h1>Product</h1>
    <Suspense fallback={<p>Loading product...</p>}><Details params={props.params} /></Suspense>
  </main>;
}

ProductPage never awaits params itself; the await happens inside the boundary, which is what lets the build emit a URL-independent shell. Against a build that ran at 06:10:33:

Output of 41
/products/1   Product 1 — built at 2026-09-22T06:10:33.483Z    (identical on every request)
/products/42  first visit :  Loading product...
                             Product 42 — built at 2026-09-22T06:11:02.739Z
              second visit:  Product 42 — built at 2026-09-22T06:11:02.739Z

Product 1 was prerendered. Product 42 was not: the first visitor got the shell immediately and the body streamed in, and the second visit was served the upgraded page with no fallback at all. A prefetch counts as that first visit, so a <Link> entering the viewport starts the upgrade before the click.

Coming from the Pages Router, the mapping is direct: getStaticPaths becomes generateStaticParams, getStaticProps with revalidate becomes "use cache" with cacheLife, fallback: true is now the default, and router.isFallback has nothing left to do.

Prerender the pages you know are popular and let the long tail generate itself. Every listed param costs build time and storage for output that may never be requested before the next deploy, and an unlisted URL is only one streamed render behind.