Measured against Next.js 16.3.8 and 16.4.0 on 2026-10-11.
Next.js 16.4 shipped on 6 October with three kinds of claim in its announcement: numbers ("Turbopack's disk cache uses 20-25% less space"), directions with no number attached ("smaller production bundles"), and defaults ("all new apps created with create-next-app will have Cache Components enabled").1
We checked all three by building the same code on 16.3.8 and 16.4.0. The short version:
- The disk cache claim is conservative. The build cache shrank 22.5% on the Tailwind starter, 28.3% on the CSS Modules starter and 39.1% on a 40-route app. The dev cache shrank 32%.
- Client JavaScript did not get smaller. It grew 0.4% on the starter and moved by a tenth of a percent on the 40-route app. Route code did shrink; the framework chunks grew by about as much.
- CSS Modules is where the bundle saving is. The starter's module stylesheet lost 10.4%.
- The starter changes in three ways the announcement does not list, and the one it does list as a default, agent feedback, is only switched on by one of the ways you can run the CLI.
ensureStaticfails the build at one of its three levels. The other two never do.
The setup
- Two installs side by side,
next@16.3.8andnext@16.4.0, each with the React version its owncreate-next-apppins (19.2.8 and 19.3.0). Node 24.18.0, macOS arm64, Turbopack. - The official starter, generated by
create-next-appin both its Tailwind and its CSS Modules variant. Theapp/directory is byte-identical between the two versions apart from the favicon, so the same source was built on both. - A larger public app:
vercel/next-app-router-playgroundat commitb5c0f7e, 40 routes, Cache Components already on.2 One route was removed from both copies, for a reason covered below. .nextdeleted before every build, one build at a time, client assets counted from.next/staticas raw bytes and at gzip level 9.- For
ensureStatic, 19 single-route apps, one build each, with the terminal output kept.
What create-next-app generates now
We ran both versions of the CLI with --yes against an empty preferences file and diffed the output.
next.config.ts on 16.3.8 is an empty object. On 16.4.0:
const nextConfig: NextConfig = {
/* config options here */
cacheComponents: true,
partialPrefetching: true,
turbopack: {
rules: {
"*.css": {
loaders: ["@tailwindcss/turbopack"],
as: "*.css",
},
},
},
};tsThe first two lines are the announced change. The turbopack.rules block is not in the release post: the Tailwind starter no longer has a postcss.config.mjs, and @tailwindcss/postcss in devDependencies is replaced by @tailwindcss/turbopack. If you copy configuration between a new project and an older one, that is the line that will not match. The remaining differences are React 19.3.0 and a smaller favicon.
Agent feedback depends on how you ran the CLI
The post says agent feedback "is enabled by default when you create a new app with the recommended create-next-app settings".1 The config reference says the option "is disabled by default".3 Both are accurate, and the difference is which path through the CLI you took.
None of our --yes scaffolds contain experimental.agentFeedback. The CLI source shows why: the flag is set from a recommendedDefaults object that is only consulted when the interactive question "Would you like to use the recommended Next.js defaults?" is answered with its first, pre-selected choice.4 In practice:
| how the app was created | agentFeedback in the config |
|---|---|
| interactive, Enter on the first prompt | yes |
| interactive, customising the settings | asked separately, the toggle starts on Yes |
--yes | no |
any explicit flag, such as --tailwind | no |
CI=1 | no |
--agent-feedback | yes |
So a project scaffolded by hand probably has it, and one scaffolded by a script or a template generator does not. When the flag is on, next dev maintains a block in AGENTS.md asking coding tools to draft issue reports; the docs state that it needs Next.js telemetry enabled, does not run in CI, and sends nothing until you press a button in a review form.3 Removing the flag is the documented way to turn it off.
A warning existing apps will meet
Anyone who enabled cacheComponents on 16.0 through 16.3 now gets this on every build and every dev start:
⚠ `cacheComponents` is enabled without a corresponding `partialPrefetching` option.
Set `partialPrefetching` to either `true` or `false`.
It is a warning, not an error, and it comes with its own advice: the only reason to choose false is "if you're migrating an older Cache Components app", because the first release of Cache Components did not include Partial Prefetching. So false keeps the behaviour you had, and true opts into the prefetching model the release post now treats as part of Cache Components.1 If you have not turned the flag on yet, the list of patterns that stop building when you do is unchanged by this release, and the new starter is the reason more people are about to meet it.
The disk cache: the claim is low
Turbopack keeps two caches: .next/cache/turbopack for next build and .next/dev/cache/turbopack for next dev, both on by default.5 The build cache is the easier one to measure, because a build writes it once and exits.
cold next build | 16.3.8 | 16.4.0 | change |
|---|---|---|---|
| starter, Tailwind | 33.0 MB | 25.6 MB | -22.5% |
| starter, CSS Modules | 33.0 MB | 23.6 MB | -28.3% |
| playground, 40 routes | 141.7 MB | 86.3 MB | -39.1% |
A second cold build of the playground on each version landed within 0.1% of the first. The claim was 20-25%,1 and the saving grew with the size of the app, which is the direction you want. On the playground the whole .next directory went from 264 MB to 208 MB.
For the dev cache we started next dev on the playground, requested the same 15 routes in the same order, and measured the directory: 602 MB on 16.3.8, 408 MB on 16.4.0, 32% less.
Getting that second number took longer than expected. On 16.3.8 the cache was on disk within 15 seconds of the last request. On 16.4.0 the directory held a single 70-byte file through three sessions of 30 to 90 seconds, and was empty after the server was interrupted. In a fourth session that we left alone, the cache appeared between 270 and 300 seconds after the last request. We do not know whether that delay is a fixed timer or a reaction to the machine, and the release post does not mention it. What we can say is that on this machine, a short dev session on 16.4.0 left nothing behind for the next one to reuse.
Bundles: smaller CSS, the same JavaScript
The announcement names two mechanisms: shorter CSS Module class names in production, and export mangling, which shortens the internal names that connect modules.1 Both are real, and visible in the output.
The class names first. This is the starter's home page HTML on each version:
<!-- 16.3.8 -->
<div class="page-module__E0kJGG__page">
<!-- 16.4.0 -->
<div class="E0kJGG_page">htmlFourteen characters shorter per use, in the stylesheet and again in the HTML. The local name survives, so production markup stays readable. On the starter:
| CSS Modules starter | 16.3.8 | 16.4.0 | change |
|---|---|---|---|
| module stylesheet | 2,419 B | 2,167 B | -10.4% |
| all CSS | 6,730 B | 6,374 B | -5.3% |
| all CSS, gzip | 2,028 B | 1,985 B | -2.1% |
prerendered index.html | 10,357 B | 10,058 B | -2.9% |
Gzip was already compressing the repeated prefix, which is why 10% raw turns into 2% on the wire. An app with hundreds of module classes will save more in absolute terms and about the same in proportion. A Tailwind app has almost nothing to gain here: the Tailwind starter's CSS moved from 14,685 to 14,581 bytes.
JavaScript did not follow.
| client JS, all chunks | raw 16.3.8 | raw 16.4.0 | gzip 16.3.8 | gzip 16.4.0 |
|---|---|---|---|---|
| starter, same config | 581,901 | 584,384 (+0.43%) | 178,780 | 180,346 (+0.88%) |
| starter, 16.4 default config | 581,901 | 585,893 (+0.69%) | 178,780 | 180,743 (+1.10%) |
| playground, 40 routes | 687,082 | 686,372 (-0.10%) | 217,533 | 218,216 (+0.31%) |
Splitting the playground's total explains it. The seven chunks every page loads, which are the framework and React, went from 582,689 to 583,886 bytes. Everything else, the code that belongs to routes, went from 104,393 to 102,486 bytes, 1.8% smaller. Mangling worked on the application code and the framework floor rose by roughly the same amount, so the total did not move.
That makes the saving proportional to how much of your bundle is your own code. On a starter it is negative. On an app whose route code outweighs the framework several times over, 1.8% of that is a real number, and this measurement cannot tell you how large. The release also says the Turbopack runtime now ships as one chunk shared across routes;1 the chunk count was the same on both versions in both apps, 10 and 21, so whatever changed there did not show up as fewer files.
ensureStatic guards one level out of three
ensureStatic is a new route segment export that takes 'shell', 'prefetch' or 'navigation'. The announcement introduces it as a way to "fail the build if any dynamic content is ever included", shows 'navigation', and adds that you can also choose the other two "if you need more fine-grained control".1 The reference is more precise: 'shell' and 'prefetch' "do not add build validation".6
We built each case to see what that means in a terminal. Every row is its own app with one page; ◐ is a partial prerender and ○ is fully static.
| the page | ensureStatic | result |
|---|---|---|
cookies() inside <Suspense> | not set | builds, ◐ |
| the same | 'shell' | builds, ◐ |
| the same | 'prefetch' | builds, ◐ |
| the same | 'navigation' | build error |
| the same, level set on the root layout instead | 'navigation' | build error |
uncached fetch inside <Suspense> | 'navigation' | build error |
server searchParams inside <Suspense> | 'navigation' | build error |
client useSearchParams() inside <Suspense> | 'navigation' | builds, ○ |
'use cache', no cacheLife | 'navigation' | builds, ○ 15m / 1y |
'use cache' + cacheLife('minutes') | 'navigation' | builds, ○ 1m / 1h |
'use cache' + cacheLife('seconds') | 'navigation' | build error |
dynamic route, no generateStaticParams | 'navigation' | build error |
dynamic route with generateStaticParams | 'navigation' | builds |
cookies() with no <Suspense> | 'shell' | build error, the same one as with no export |
layout 'navigation', page 'shell' | both | build error |
a static page, cacheComponents off | 'navigation' | build error |
'shell', Partial Prefetching off | 'shell' | builds, prints "has no effect" |
Read the first four rows together. A blog that sets ensureStatic = 'shell' on its root layout and later gains a component that reads a cookie inside a boundary will keep building. The two lower levels change what the client router asks for; they are not a guard. If the goal is the one in the announcement, a build that breaks when request-time rendering sneaks in, 'navigation' is the only value that delivers it.
Every 'navigation' failure caused by data prints the same text:
Error: Route "/": Next.js encountered uncached or runtime data on a route that
must be fully static.
This route is configured to be fully static, but some data requires rendering
at request time.
Ways to fix this:
- [cache] For uncached data (`fetch`, database calls): cache the access with
`"use cache"` (does not apply to `connection()`)
- [remove] Remove the data access
- [client] Read the data on the client
For cookies(), the first suggestion is not available: a cached scope cannot read request data, so the real options are the second and third. The cacheLife('seconds') case gets this message too, even though the data is already inside 'use cache'. The rule it broke is in the reference, not in the error: a cached scope whose expire is under five minutes does not count as static.6 If you see "uncached data" on a line you have already cached, check the lifetime before anything else.
The structural errors are clearer. A missing generateStaticParams names the page and the fix. Conflicting levels print both exports and the files they came from:
A child segment cannot override a parent segment with a less-constrained `ensureStatic`.
Parent has:
export const ensureStatic = "navigation"
(from: [project]/app/layout.tsx)
Child has:
export const ensureStatic = "shell"
(from: [project]/app/page.tsx)
One rename if you tracked the canary
The playground is pinned to 16.4.0-canary.12. On stable 16.4.0 it compiles and then fails the type check:
Module '"next/cache"' has no exported member 'unstable_navigation'.
Module '"next/cache"' has no exported member 'unstable_prefetch'.
The two functions shipped as navigation and prefetch.1 They do not exist on 16.3 under either name, which is why we removed that one route from both builds before comparing sizes. If you adopted them from a canary, the fix is the import.
What to do, in order
- Upgrade for the cache alone if disk or CI cache size matters to you. It needs no configuration and it was the largest effect we measured. If your CI restores
.next/cache, expect that directory to be a quarter to a third smaller. - Decide
partialPrefetchingexplicitly if you already runcacheComponents.falseis the no-change answer and it silences the warning. - Do not expect a smaller JavaScript bundle. Compare your own
.next/staticbefore and after; if most of it is framework, it will not move. If you use CSS Modules, the stylesheet and the HTML will. - Use
ensureStatic = 'navigation'when you want a guard. Put it on the layout that covers the routes that must stay static, and treat'shell'and'prefetch'as router settings. - Check
next.config.tson anything scaffolded this week. New apps have Cache Components on, which means different rules for uncached data from the first commit, and a different prefetch model from the one older tutorials describe. - Leave room for another upgrade. The Next.js team has announced an out-of-band security release for 14 October covering two Critical and one High severity issue in upstream dependencies, with advisories to be published alongside it.7
Where this is weak
- Two apps. A starter and a 40-route demo, both from Vercel. The JavaScript result in particular depends on the ratio of framework to application code, and neither app looks like a large product.
- One machine, one day. macOS arm64, Node 24.18.0, an external APFS volume. The dev-cache timing is the finding most likely to be specific to this setup, and we did not run it enough times to call it a rule.
- Sizes, not speed. We did not benchmark compile times, memory, or the lazy server HMR and lazy compilation changes. A smaller cache is not evidence of a faster restart.
- Different React versions. Each Next.js version was built with the React its own starter pins. The App Router bundles its own React build, so the framework growth belongs to the release as a whole and cannot be split between Next.js and React from these numbers.
ensureStaticwas probed for whether it builds. We did not measure what'shell'and'prefetch'change in the requests a browser makes, which is the thing they exist to do.- The experimental flags are untested here: the Rust React Compiler,
turbopackGc, lazy dynamic imports and worker threads.
FAQ
Is Cache Components enabled by default in Next.js 16.4?
Only in new apps. create-next-app 16.4.0 writes cacheComponents: true and partialPrefetching: true into next.config.ts; upgrading an existing app changes neither. The release post says it becomes the framework default in Next.js 17.1
How much smaller is the Turbopack cache in 16.4?
In our builds, 22.5% to 39.1% for the build cache and 32% for the dev cache, against a claimed 20-25%.1 The larger the app, the larger the saving.
Does Next.js 16.4 reduce JavaScript bundle size?
Not measurably in the two apps we built. Route code shrank 1.8% from export mangling while the shared framework chunks grew 0.2%, and on a framework-heavy bundle those cancel. CSS Modules output did shrink, by about 10% raw.
What does ensureStatic do?
It requires part of a route's server output to be static. With 'navigation' the build fails if the route reads cookies(), headers(), server searchParams, uncached data, or a cache that expires in under five minutes. With 'shell' or 'prefetch' nothing is validated at build time.6 It only works with cacheComponents enabled.
Why does my build warn about partialPrefetching?
Because cacheComponents is on and partialPrefetching is not set. 16.4 wants an explicit true or false. false keeps the previous behaviour.
Does create-next-app turn on agent feedback?
Only if you accept the interactive "recommended defaults" prompt or pass --agent-feedback. --yes, any explicit flag, and CI all leave it off.4 It can be removed from next.config.ts at any time.3
Sources
Checked 2026-10-11.
Sources
-
Next.js blog: Next.js 16.4 - that the release was published on 6 October 2026; that "Turbopack's disk cache uses 20-25% less space" through Zstandard compression and improved compaction; that Turbopack "generates shorter CSS Module class names in production" and "uses export mangling"; that the runtime ships "in a single chunk shared across routes"; that "all new apps created with
create-next-appwill have Cache Components enabled by default" and that it "will become the default in Next.js 17"; that Partial Prefetching is "now considered part of the model"; that agent feedback "is enabled by default when you create a new app with the recommendedcreate-next-appsettings"; the wording used to introduceensureStaticand its three values; that the release addsnavigationandprefetchtonext/cache; and that it ships with React 19.3. -
vercel/next-app-router-playground - the public App Router demo used as the larger test app, at commit
b5c0f7eaf1ed070d20aa29f435ce22a48b987684, which pinsnextto16.4.0-canary.12and enablescacheComponentsandpartialPrefetching. -
Next.js docs: agentFeedback - that
experimental.agentFeedback"is disabled by default and requires Next.js Telemetry to be enabled", that it "does not run in CI", thatnext devcreates and updates a managed block inAGENTS.mdwhen it is on, that nothing is sent when the review form opens, and that removing the option or setting it tofalsemakes the nextnext devrun remove the block. -
vercel/next.js at v16.4.0: packages/create-next-app/index.ts - that
agentFeedback: truelives in arecommendedDefaultsobject separate from the defaults used by--yes, that it is applied only when the "Would you like to use the recommended Next.js defaults?" prompt returnsrecommended, that the prompt is skipped when any flag is passed or in CI, and that the standalone agent feedback question is asked only in a fully interactive run. -
Next.js docs: turbopackFileSystemCache - that the dev cache lives in
.next/dev/cache/turbopackand the build cache in.next/cache/turbopack, that both options default totrue, and that file system caching has been on by default for development since 16.1.0 and for builds since 16.3.0. -
Next.js docs: ensureStatic - that the export accepts
'auto','shell','prefetch','navigation'orfalse; that it "only works whencacheComponentsis enabled"; that'shell'and'prefetch'"do not add build validation" while'navigation'is validated during bothnext devandnext build; that under'navigation'uncached data,cookies(),headers(), server-sidesearchParamsand ause cachescope whoseexpireis under five minutes all fail validation even inside<Suspense>; that a dynamic route must exportgenerateStaticParams(); that client hooks such asuseSearchParams()are allowed; and that a child segment cannot set a weaker level than its parent. -
Next.js blog: Upcoming Next.js Security Update for Upstream Vulnerabilities - that an out-of-band security update is planned for 14 October 2026 addressing "two Critical and one High" severity vulnerabilities in upstream dependencies, and that full advisories with affected versions will be published with the update.