Skip to content
What's New in Next.js 16.4

What's New in Next.js 16.4

Next.js 16.4 makes Cache Components the default for new apps. Here's what changed, from ensureStatic to agent upgrades and Turbopack, in plain English.

11 min read

TL;DR: Next.js 16.4 recommends Cache Components for every app and enables them by default in create-next-app. It adds ensureStatic to guarantee static routes, navigation() and prefetch() to control what loads during prefetching, an agent-driven upgrade command, and free Turbopack improvements like a 20–25% smaller dev cache.

Every few months Next.js ships a new release, and every time I open the release notes I understand about half of it on the first read. Version 16.4 landed on October 6, 2026, and this one has a lot packed in: new route configs, AI agents that upgrade your app, a pile of Turbopack internals, and a big change in status for a caching model that's been around for a year.

Most of it boils down to one decision and a handful of free wins. Cache Components isn't new (it shipped in Next.js 16), but until now it came with a "not for every app yet" asterisk. In 16.4 the Next.js team drops the asterisk, recommends Cache Components for every Next.js app, and switches it on for every new project made with create-next-app. The rest of the release either supports that model or makes your dev server lighter.

Next.js 16.4 at a glance

If you only have a minute, this table covers it.

ChangeWhat it meansDo you need to act?
Cache Components recommendedCaching is opt-in, per component, with 'use cache'New apps: already on. Existing apps: migrate when ready
ensureStaticBuild fails if a page you want static becomes dynamicOptional, great for blogs and marketing sites
navigation() and prefetch()Choose what loads during a prefetch vs a real clickOptional
next upgrade --agentYour AI coding agent runs the upgrade for youOptional
Turbopack improvementsSmaller cache, less dev work, smaller bundlesNo, you get them by upgrading
React 19.3Stable View Transitions and Fragment RefsNo, it ships with 16.4

What are Cache Components in Next.js?

Before Cache Components, the App Router cached things for you implicitly, and figuring out why a page was stale was a hobby in itself (ask anyone who debugged a fetch cache in Next.js 13). Cache Components flips that: nothing is cached unless you say so.

You mark a component or function with 'use cache', and set how long it stays fresh with cacheLife(). The Next.js team describes it as a component-level version of the Cache-Control header, which is the cleanest mental model I've seen for it.

app/dashboard/page.tsx
import { cacheLife } from "next/cache";
import { Suspense } from "react";

export default async function DashboardPage() {
  const user = await getCurrentUser(); // runs on every request

  return (
    <div>
      <p>Welcome, {user.name}</p>
      <Suspense fallback={<p>Loading projects…</p>}>
        <Projects userId={user.id} />
      </Suspense>
    </div>
  );
}

async function Projects({ userId }: { userId: string }) {
  "use cache";
  cacheLife("hours"); // cached for hours, per userId

  const projects = await getProjects(userId);
  return projects.map((p) => <p key={p.id}>{p.name}</p>);
}

The welcome message is personal, so it renders on every request, while the project list comes from the cache. Both arrive in the same response.

Cache Components isn't new in 16.4

You may have seen 'use cache' before. Cache Components has been building up over several releases, and 16.4 is where it stops being optional advice:

VersionWhat happened
Next.js 15'use cache' appears as an experimental feature behind the experimental.dynamicIO flag
Next.js 16 (October 2025)Ships as Cache Components behind cacheComponents: true. dynamicIO and the old ppr flags are removed
Next.js 16.3 (2026)Adds Partial Prefetching, which prefetches one reusable shell per route instead of every link
Next.js 16.4 (October 2026)Recommended for every app, on by default in create-next-app
Next.js 17Becomes the default for all apps

What 16.4 adds is a fix for the leftover gaps. There were cases where Cache Components couldn't match the cost and performance of the old model, and the features below (ensureStatic, navigation(), prefetch()) close them.

How to turn it on

New apps from create-next-app already have it. For an existing app, add two flags:

next.config.ts
import type { NextConfig } from "next";

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

export default nextConfig;

partialPrefetching arrived in Next.js 16.3 (opens in new tab) and is now considered part of the model, so turn on both. Cache Components becomes the default in Next.js 17, so you're migrating either way. I'd rather do it on my own schedule than during a major version bump.

How to keep pages static with ensureStatic

Mixing static and dynamic content is powerful, but it has one sneaky downside. Someone adds a <UserAvatar /> to your blog post layout, and suddenly every blog page renders on the server at request time. Your hosting bill notices before you do.

ensureStatic is a guardrail for that. Export it from a page or layout with the value "navigation", and Next.js errors in next dev and fails next build if anything on that route needs request-time rendering:

app/about/page.tsx
export const ensureStatic = "navigation";

export default function Page() {
  return (
    <>
      <UserAvatar /> {/* reads cookies, so the build fails */}
      <Content />
    </>
  );
}

Wrapping <UserAvatar /> in <Suspense> doesn't get you out of it. With "navigation", uncached fetch or database calls, cookies(), headers(), server-side searchParams, and short-lived caches (a cacheLife that expires in under five minutes) all fail the check. Data cached with 'use cache' is fine.

There are three levels. Each stricter level includes the ones below it:

  • "navigation": the whole server-rendered route must be static. This is the only level that fails the build, and it works with Cache Components alone.
  • "prefetch": the shell and per-link prefetches (<Link prefetch>) must come from static output.
  • "shell": only the shell that every plain <Link> loads must be static.

"prefetch" and "shell" only do something when Partial Prefetching is on, and they don't add build validation. Despite the name, they tell Next.js to serve those stages from static output rather than guaranteeing it at build time.

A few rules to know before you add it:

  1. It needs cacheComponents: true, or the build fails.
  2. With "navigation", a dynamic route like app/blog/[slug]/page.tsx needs a generateStaticParams() that returns at least one full set of params, or the build fails.
  3. A child segment can be stricter than its parent but never looser. If app/blog/layout.tsx sets "prefetch", a page under it can set "navigation", but not "shell".

That third rule shapes how you roll it out. Setting "navigation" on the root layout locks the entire site to static, and no nested page can opt out. If some pages need dynamic data later, move the export down from the root layout to the layouts that should stay static (like app/blog/layout.tsx) instead of trying to override it.

For blogs, docs, ecommerce product pages, and marketing sites, I'd add this today. A build error is a much cheaper way to find out than a surprise bill.

How to stop prefetching from loading too much

Prefetching makes navigation feel instant. <Link prefetch> loads a page's cached UI before the user clicks, so there's no spinner. The catch: it loads everything cached on that page, for every visible link, including pages the user never opens.

Picture an email inbox with 50 visible messages. With prefetch on, you load 50 full email threads up front. That's a lot of database work for emails nobody reads.

Next.js 16.4 adds navigation(). Await it inside a component, and that component is skipped during prefetching and only loads on a real click:

app/message/[id]/page.tsx
import { navigation } from "next/cache";
import { Suspense } from "react";

async function Message({ id }: { id: string }) {
  const message = await getMessage(id); // included in the prefetch

  return (
    <>
      <h1>{message.subject}</h1>
      <div>{message.body}</div>
      <Suspense fallback={<p>Loading thread…</p>}>
        <Thread id={id} />
      </Suspense>
    </>
  );
}

async function Thread({ id }: { id: string }) {
  await navigation(); // skipped during prefetch, loads on click
  const thread = await getThread(id);
  return thread.map((m) => <div key={m.id}>{m.body}</div>);
}

Now the prefetch loads the first message (instant open), and the rest of the thread waits until someone clicks.

There's also a sibling, prefetch(), imported from the same place. Await it to keep content out of the route's shell (the part every plain <Link> loads), so it only renders on an explicit <Link prefetch> or a real navigation. Rule of thumb: navigation() means "wait for the click," prefetch() means "wait for an explicit prefetch."

Both throw without cacheComponents, and both are built for Partial Prefetching. Without partialPrefetching, prefetch() does nothing and <Link prefetch> fetches the whole route anyway, deferred thread included, which undoes the inbox fix. They also throw inside a 'use cache' function. Await them in the component, then call your cached functions below that line, like Thread does above.

How to upgrade to Next.js 16.4

You have two options. The classic one:

Terminal
npm install next@latest

Or let your AI coding agent do it:

Terminal
npx next@canary upgrade --agent=latest

The --agent mode checks your installed version, picks the target release, and hands your agent the migration guides, codemods, and verification steps. The agent applies the upgrade, fixes what breaks, and checks the app still works. Using next@canary here means you get the newest upgrade tooling even if your app is several versions behind.

Two related experimental flags are worth knowing about:

  • experimental.agentUpgrade: next dev and next build remind you when an upgrade is available. The default, "security", only nags about known vulnerabilities in your version. "latest" reminds you about every minor and major release. false turns it off.
  • experimental.agentFeedback: when your agent hits a framework bug or unclear docs, it drafts a report for the Next.js team. You review each draft in the browser, and nothing is sent until you click Send feedback. It's on by default for new apps created with the recommended create-next-app settings, needs Next.js telemetry enabled, and doesn't run in CI.

If you're migrating an existing app to Cache Components, the Next.js team also publishes agent Skills for Cache Components adoption (opens in new tab) that walk your agent through the refactor.

Free improvements you get by upgrading

None of these need config changes:

  • The Turbopack disk cache is 20–25% smaller. It now uses Zstandard compression for the bulky data and cleans out stale entries more aggressively.
  • Server HMR is lazy. Edit a shared server file, and only the page you're looking at recompiles; pages you visited earlier wait until you open them again.
  • Turbopack ships its runtime as one chunk shared across routes, so browsers download it once and cache it better.
  • Production bundles are smaller, thanks to shorter CSS Module class names and export mangling that shortens internal JavaScript export names.
  • React 19.3 comes along, with stable View Transitions, Fragment Refs, and a new browser() API. The React 19.3 announcement (opens in new tab) has the details.

If you're chasing Lighthouse scores, the bundle changes stack nicely with the techniques in my Next.js optimization guide.

Experimental Turbopack flags worth trying

These sit behind flags, so treat them as "try in a branch first." All of them go under experimental in next.config.ts.

FlagWhat it does
turbopackRustReactCompiler: trueRuns the React Compiler in Rust instead of Babel. 30% less memory and 15% faster compiles in this release. Needs reactCompiler: true
turbopackGc: trueGarbage-collects unused compile work from memory and disk, including leftovers from deleted routes
turbopackLazyDynamicImports: trueCompiles client-side import() code only when the browser asks for it
turbopackPluginRuntimeStrategy: "workerThreads"Runs Babel, PostCSS, and webpack loaders in worker threads instead of separate processes
turbopackAdditionalRootsFollows symlinked packages outside your project root, including pnpm and Bun global stores

One gotcha: on Node.js 24.13.1 and newer, the worker threads option falls back to child processes because of a Node.js bug (opens in new tab). So if you turn it on and see no difference, that's probably why.

The Turbopack Bundle Analyzer also got a big update: a route summary page that shows your largest client routes, a sortable table view, automatic snapshots you can diff over time, and markers for modules on the critical render path (with an option to filter out async dependencies). There's an agent skill for it too:

Terminal
npx skills add vercel/next.js --skill next-bundle-optimizer

Then run /next-bundle-optimizer in your agent.

Should you upgrade to Next.js 16.4?

Yes. The upgrade itself is low-risk: the Turbopack and bundle improvements need no changes, and anything that changes behavior sits behind a flag. Bump the version and run your build.

The bigger question is Cache Components, and my answer depends on where you are. Starting something new? It's on by default, so keep it. Running an existing app? Start the migration now while it's optional, one route at a time, and add ensureStatic to the pages you know should never be dynamic. When Next.js 17 makes it the default, you'll already be done instead of migrating under pressure.

If you want help untangling caching in a real codebase, or you've got a Next.js app that's slower than it should be, get in touch.

Frequently asked questions

What is new in Next.js 16.4?
Next.js 16.4 recommends Cache Components for every app and turns them on by default for new projects. It adds the ensureStatic route config, the navigation() and prefetch() functions for controlling prefetching, next upgrade --agent for AI-assisted upgrades, React 19.3, a 20–25% smaller Turbopack disk cache, lazy server HMR, and smaller production bundles.
What are Cache Components in Next.js?
Cache Components is the Next.js programming model where caching is opt-in. You mark a component or function with the 'use cache' directive and set how long it stays fresh with cacheLife(). Everything else renders at request time. Cached and dynamic parts can stream together in a single response.
Are Cache Components new in Next.js 16.4?
No. 'use cache' first appeared as an experimental feature in Next.js 15, and Cache Components shipped behind the cacheComponents flag in Next.js 16 in October 2025. Next.js 16.3 added Partial Prefetching. What's new in 16.4 is the status: the Next.js team now recommends Cache Components for every app, and create-next-app enables it by default.
How do I enable Cache Components in Next.js 16.4?
Set cacheComponents: true and partialPrefetching: true in next.config.ts. New apps created with create-next-app in 16.4 have this enabled already. Cache Components will become the default in Next.js 17.
What does ensureStatic do in Next.js?
ensureStatic is a route segment config, new in Next.js 16.4, that requires parts of a route to be static. With 'navigation', the build fails if anything on the route needs request-time rendering. 'prefetch' and 'shell' serve per-link prefetches or the App Shell from static output when Partial Prefetching is on. It requires cacheComponents, applies to every page under a layout, and child segments can only be stricter, never looser.
How do I upgrade to Next.js 16.4?
Run npm install next@latest to upgrade manually, or run npx next@canary upgrade --agent=latest to have your coding agent handle the upgrade, including migration guides, codemods, and verification steps.
Is Next.js 16.4 a breaking upgrade?
No. Cache Components is still opt-in for existing apps through the cacheComponents flag. The Turbopack improvements and React 19.3 apply without config changes, and the new Turbopack features like garbage collection and lazy dynamic imports sit behind experimental flags.