←All posts

Next.js 16.4 Makes Cache Components the Default Recommendation: ensureStatic and Prefetch Control Explained

Next.js 16.4 recommends Cache Components for every app and adds two performance guardrails: ensureStatic to keep routes static, and navigation() to keep expensive data out of prefetches. Here is what they do and how to adopt them.

Next.js 16.4 Makes Cache Components the Default Recommendation: ensureStatic and Prefetch Control Explained

For several releases, the Next.js team has described Cache Components as an opt-in programming model with a few gaps. With Next.js 16.4, published October 6, that hedge is gone: the team now recommends Cache Components for every Next.js app, new projects created with create-next-app have it enabled by default, and the post states it will become the default in Next.js 17.

If you run a production App Router app, this is the release to read carefully. The headline is the recommendation, but the practical value is in two new controls that address the two classic ways a fast Next.js site gets slow: a dynamic component sneaking into a page that should be static, and prefetching that pulls far more data than users ever look at.

A quick refresher on Cache Components

Cache Components lets you mark parts of the component tree as cacheable with the 'use cache' directive. The Next.js team describes it as a component-level version of the Cache-Control HTTP header. Combined with cacheLife, it looks like this:

import { cacheLife } from 'next/cache';

async function Projects({ userId }) {
  'use cache';
  cacheLife('hours');

  const projects = await db.query.projects.findMany({
    where: eq(projectsTable.userId, userId),
  });

  return (
    <div>
      {projects.map((project) => (
        <p key={project.id}>{project.name}</p>
      ))}
    </div>
  );
}

The model replaces the implicit caching behaviors of earlier App Router versions with explicit, composable annotations. A single page can mix cached UI, pluggable server-side caching, and request-time rendering, streaming the static and dynamic parts together in one response.

To use it in an existing app today, enable two flags:

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

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

export default nextConfig;

Partial Prefetching shipped after Cache Components originally did, but the 16.4 post treats it as part of the model, so turn both on together.

ensureStatic: a build-time guarantee

The flexibility of mixing static and dynamic content has a cost. On a marketing page, a blog, or a product catalog, one request-time component can quietly turn an otherwise cacheable route into one that does work on every visit. Nothing breaks; your server bill and your latency just creep up.

ensureStatic turns that into a build failure. You export it from a route segment:

export const ensureStatic = 'navigation';

export default function Page() {
  return (
    <>
      <UserAvatar /> {/* dynamic: this fails the build */}
      <Content />
    </>
  );
}

There are three levels, from strictest to most targeted:

  • 'navigation' guarantees that navigations to the route never render at request time.
  • 'prefetch' guarantees that links with explicit prefetching only fetch static content.
  • 'shell' guarantees that only static content is fetched when the route is first discovered.

You can also put it in a layout to apply the guarantee to every page under it. Setting export const ensureStatic = 'navigation' in the root layout asserts that every page navigation on the site is static, and you can then push the setting down to nested layouts where a section legitimately needs dynamic content.

The Next.js team is explicit that many dynamic apps will not need this. It is aimed at the sites where cost and speed depend on staying static: ecommerce storefronts, marketing sites, and blogs. For those, it works like a lint rule for your caching strategy, enforced by the build rather than by code review.

navigation() and prefetch(): control what a prefetch loads

Prefetching is how Next.js removes loading states: <Link prefetch> or useRouter().prefetch() renders a page's cached UI before the user clicks. The trade-off is that every visible link now loads everything that page caches.

The post uses an email app as the example. If each inbox link prefetches its full message thread, the app loads entire threads for messages nobody opens. Turning prefetching off brings the spinner back. The 16.4 answer is a third option: keep prefetching, but exclude the expensive part until a real navigation happens.

import { navigation } from 'next/cache';

async function Message({ id }) {
  const message = await getMessage(id);

  return (
    <>
      <p>{message.subject}</p>
      <div>{message.body}</div>

      <Suspense fallback={<Spinner />}>
        <Thread id={id} />
      </Suspense>
    </>
  );
}

async function Thread({ id }) {
  await navigation();
  const thread = await getThread(id);

  return (
    <>
      {thread.map((message) => (
        <div key={message.id}>{message.body}</div>
      ))}
    </>
  );
}

Awaiting navigation() excludes the component from prefetching, so the prefetch renders only the first message and the thread loads when the user actually navigates. A companion prefetch() function does the same one stage earlier: awaiting it excludes cached content from a route's shell, deferring it until an explicit prefetch via <Link prefetch> or useRouter().prefetch().

This gives you a way to budget work per stage: what loads when a route is discovered, what loads on prefetch, and what waits for the click. For apps with long lists of links and heavy detail pages, that is a direct lever on both backend load and perceived speed.

Build and bundle improvements that need no code changes

Beyond Cache Components, 16.4 includes several improvements that apply to every app:

  • Turbopack's disk cache uses 20 to 25 percent less space, thanks to Zstandard compression of the bulk of the data while keeping LZ4 for metadata.
  • Server HMR is lazy: Turbopack applies server updates only when a request needs them, instead of refreshing previously visited pages in the background.
  • Turbopack ships its runtime as a single chunk shared across routes, which the team says reduces download sizes and improves cache hit rates.
  • Production CSS Module class names are shorter, and export mangling shortens internal JavaScript export names, both reducing bundle size.
  • React 19.3 is included, with stable View Transitions and Fragment Refs.

The updated Turbopack Bundle Analyzer also adds a route summary of the largest client routes, a sortable table view, snapshots you can diff over time, and marking of modules on the critical render path. These are worth running before and after any Cache Components migration so you can see the effect on shipped JavaScript.

Several other items, such as the Rust React Compiler, a disk cache garbage collector, and lazy dynamic imports, are experimental behind flags. Treat them as things to try on a branch, not to roll out.

What to do this week

  1. Start a branch, not a migration. Enable cacheComponents and partialPrefetching on a staging branch and see what the build tells you about your routes before touching production.
  2. Pick your static routes and guard them. Add ensureStatic to the layouts of marketing, blog, and catalog sections. If the build fails, you have found a dynamic component that was costing you.
  3. Audit your prefetched links. Find pages where <Link prefetch> pulls heavy data, and move the expensive part behind await navigation().
  4. Use the upgrade tooling if it helps. The release adds npx next@canary upgrade --agent=latest, which prepares version-specific guidance for a coding agent, and the team offers Skills for adopting Cache Components. Review whatever an agent changes like any other pull request.
  5. Measure. Capture bundle sizes and Core Web Vitals before and after, so the migration is justified by numbers rather than by the release notes.

The significance of 16.4 is less any single API than the shift in posture. The framework is now telling you that explicit, declarative caching is the intended way to build, and giving you build-time tools to keep it that way. Teams that adopt it deliberately will get fast pages that stay fast as the codebase grows.

Sources: Next.js 16.4 release post; Next.js caching guide; Keeping pages static.

←Back to all posts
Next step

Need help shipping software?

Tell us what you're trying to build. A discovery call, a one-page summary within 48 hours, a proposal within a week.

Response · 48h·NDA on request·US contracts only