Next.js 16.4 ensureStatic: Keep Pages Static on Purpose

Next.js 16.4's ensureStatic fails the build when dynamic code sneaks into a static route. How each value works, what counts as dynamic, and a step-by-step way to adopt it.

Ananya RaoAnanya RaoAuthor6 November 20264 min read 2 views
Next.js 16.4 ensureStatic: Keep Pages Static on Purpose
In this article▾
  1. Some context: Cache Components is now the recommended model
  2. The basic usage
  3. Which value should you use?
  4. What counts as "dynamic" (the gotchas)
  5. Dynamic routes need generateStaticParams
  6. Personal bits can still live in the browser
  7. Layouts: set it once, tighten below
  8. A quick adoption plan
  9. Worth it?

You build a fast, fully static marketing page. Three months later someone adds a small "Hi, Ananya" greeting that reads a cookie, and the whole route quietly starts rendering on every request. Nobody notices until the hosting bill or the response times do. Next.js 16.4, released on 6 October 2026, adds a guard for exactly this: the ensureStatic route segment config. Set it, and the build fails if dynamic work sneaks into a route that should stay static. Here is how it works, which value to pick, and the gotchas that will trip you up the first time.

ensureStatic belongs to Next.js's newer programming model. The Next.js 16.4 release post says that starting with this version the team recommends Cache Components "as the best choice for every Next.js app", that new apps from create-next-app have it on by default, and that it will become the default in Next.js 17.

In short, Cache Components lets you mark parts of the tree as cacheable with 'use cache', and mix cached, static and request-time content in one page. You enable the full model with two flags:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

export default nextConfig;

That flexibility is great until a route you meant to be static stops being static. If you are still on 15, start with our guide to upgrading to Next.js 16.

The basic usage

Export ensureStatic from a page or a layout:

// app/blog/[slug]/page.tsx
export const ensureStatic = 'navigation';

export default async function Page({ params }) {
  const { slug } = await params;
  const post = await getPost(slug); // must be cached with 'use cache'
  return <Article post={post} />;
}

With 'navigation', the complete server-rendered route has to be produced at prerender time. If anything dynamic gets in, Next.js reports it in next dev and fails next build.

Two prerequisites from the ensureStatic reference: it only works when cacheComponents is enabled (the build fails otherwise), and exporting it from a Client Component throws an error.

Which value should you use?

The export takes 'auto' (the default, no extra requirement), 'shell', 'prefetch', 'navigation' or false. Each stricter level includes the earlier ones.

  • 'navigation': the whole route must be static. Works with Cache Components alone, no Partial Prefetching needed. Use it for blog posts, docs, marketing and product listing pages.

  • 'prefetch': the app shell and per-link prefetches must be static, but request-specific content behind <Suspense> can render when the user actually follows the link. Needs Partial Prefetching.

  • 'shell': only the app shell a default <Link> loads must be static. Without Partial Prefetching there is no shell, so this does nothing and Next.js logs a warning.

  • false: keeps normal rendering and fails the build if another segment tries to apply a static level. A way of saying "this route is deliberately dynamic".

My rule of thumb: start with 'navigation' on anything that has no reason to know who the user is. Reach for 'shell' or 'prefetch' only on pages that genuinely mix static content with personal data, like a dashboard with a static frame.

What counts as "dynamic" (the gotchas)

With 'navigation', the reference lists what fails validation, even if it is wrapped in <Suspense>:

  • Uncached data: a fetch or database call without 'use cache', and connection().

  • Runtime data: cookies(), headers() and server-side searchParams.

  • A 'use cache' scope with a very short life: expire under five minutes, stale under 30 seconds, or revalidate of 0.

  • Passing a promise for request-dependent server data to a Client Component, even if the component never reads it.

The same rules apply inside generateMetadata and generateViewport, which is where I would expect most first-time failures. A metadata function that reads a header for locale will break a static route.

Dynamic routes need generateStaticParams

For a route like /blog/[slug] with 'navigation', you must export generateStaticParams() and return at least one complete parameter set:

export async function generateStaticParams() {
  const posts = await getAllPostSlugs();
  return posts.map((slug) => ({ slug }));
}

Slugs not returned at build time are generated on first request, and Next.js waits for the static result before serving it.

Personal bits can still live in the browser

A static route can still show a greeting. Move the personal part into a Client Component that loads it in the browser, inside <Suspense>. The docs mention use(browser()) as allowed for this, along with client hooks like useSearchParams(). The prerender includes the fallback, and the browser fills in the rest.

Layouts: set it once, tighten below

A value on a layout applies to every route under it, including nested layouts and parallel routes. Children can be stricter but not weaker:

// app/blog/layout.tsx
export const ensureStatic = 'prefetch';

// app/blog/[slug]/page.tsx
export const ensureStatic = 'navigation'; // allowed: stricter

Go the other way and the build stops with: A child segment cannot override a parent segment with a less-constrained `ensureStatic`. If your whole site is content, putting 'navigation' on the root layout and relaxing nothing is a strong default. When one section needs to be dynamic, restructure so it sits under a different layout.

A quick adoption plan

  1. Upgrade to the latest 16.4.x patch. The Next.js blog lists a scheduled security release for 3 November 2026, so check you are on the patched version; our earlier note on which Next.js version you need explains how these releases work.

  2. Turn on cacheComponents if it is not already on.

  3. Add ensureStatic = 'navigation' to one simple content route and run next dev. Fix what it reports.

  4. Run next build --debug-prerender if the build error is too terse; the docs recommend it for fuller stack traces.

  5. Move up to the layout once a few routes pass.

Worth it?

Yes, for any route whose value depends on being cheap and fast. ensureStatic turns "we should keep this page static" from a code-review comment into a build check, and checks are what survive team changes. It is less useful on genuinely personal pages, where 'prefetch' and the new navigation() and prefetch() functions give finer control. Since 16.4 also ships React 19.3, it is a good moment to try the stable View Transitions on the same pages.

Ananya Rao

Written by

Ananya Rao

Keep reading

More from Ananya Rao

More from Ananya Rao

More in Web Development

Have a story of your own?

Publisha is free to start. Write with AI that keeps your voice, and publish in a click.