DEV Community

Cover image for Next.js 16.3's Memory Claim Didn't Hold Up on My App. Its TypeScript Claim Cut My Build by Two Thirds.
Giu
Giu

Posted on

Next.js 16.3's Memory Claim Didn't Hold Up on My App. Its TypeScript Claim Cut My Build by Two Thirds.

The claim

Next.js 16.3 shipped on August 3, 2026, and the headline number is memory: long
development sessions, Vercel says, now use up to 90% less RAM. The release notes
list more than that — repeat builds that read unchanged artifacts straight from
cache, type checking that can hand off to TypeScript 7, a server handling
roughly 22% more requests under load, and a new set of navigation primitives
called Instant Navigations.

Anyone who's watched a dev server climb until Node gives up and prints FATAL
ERROR knows why the memory line is the one that matters. It's also the hardest
to verify. "Up to 90%" was measured on Vercel's workloads, not yours, and two
weeks after release nobody had published independent numbers from a real app.

So I upgraded one. What follows is a single production app taken from 16.2.3 to
16.3.1, every build time and memory reading recorded before and after, same
machine and same procedure on both sides, three runs each.

The short version: the memory claim didn't survive contact with this app. The
TypeScript claim did, and it turned out to matter far more — it cut two thirds
off the build. The headline number and the useful number weren't the same
number.

All raw data, the measurement scripts, and the full upgrade log are in the repo: https://github.com/pfgiiu/artigo01

Methodology

The subject is a production Next.js app that builds 142 static pages on the App
Router with Turbopack. Not a scaffolded demo: a scaffold has almost no
dependency graph, and the dependency graph is precisely what eats memory.

Environment: Windows 11 Pro, Node v24.13.1, npm 11.8.0, TypeScript 5.9.3. The
baseline is Next.js 16.2.3 with React 19.2.4; the target is 16.3.1. Only the
framework version changes between runs.

Build time

Each build runs twice with .next deleted beforehand. The first is cold. The
second follows immediately, with the OS file cache warm and Next.js able to read
unchanged artifacts from its own cache. I report both, because the distance
between them is itself one of the release's claims.

Memory

Windows doesn't expose RSS. The equivalent is PeakWorkingSet64, and since the
dev server runs across three Node processes, every figure below is the sum
across the whole process tree — a single-process reading would quietly throw
away most of the footprint.

Sampling runs every 10 seconds through a PowerShell script that starts the
server, waits for readiness on port 3000, tracks the process tree, and writes
results to JSON. Before each run the port is confirmed free and no stray Node
processes are alive, so nothing from a previous session inflates the numbers.

Two scenarios:

Scenario A — Idle. npm run dev started fresh and left alone for ten
minutes with no HTTP requests.

Scenario B — Active HMR. The route compiles once, then a real edit lands in
a mounted, visible component every two minutes for ten minutes — five edits
total — each followed by a GET to force recompilation. The target is the hero
heading in the home page component, chosen because it renders in a mounted
client component, which guarantees Fast Refresh actually fires instead of the
change being discarded. The file is restored with git checkout afterward, and
every edit is logged verbatim.

Both scenarios exist because the claim is about long development sessions, not
idle servers. An idle reading alone would understate what the release changes,
and reporting it as though it addressed the claim would be the wrong measurement
dressed up as the right one.

TypeScript

The type-check phase is measured separately from compilation, since 16.3 prints
both explicitly. TypeScript 7 is tested on a throwaway branch, with and without
experimental.useTypeScriptCli, then removed — the shipped tree stays on 5.9.3.

Baseline: Next.js 16.2.3

Both baseline builds exit clean, no warnings, and the dev server readied in
1497ms with nothing in the log. That matters for what follows: this isn't a
broken project where any change looks like an improvement.

Metric 16.2.3
Cold build (.next deleted) 95.3s
Second build (cache warm) 63.8s
Dev ready 1497ms
Static pages generated 142
Peak working set — idle 355.7 MB (3 processes)
Peak working set — active HMR 564.8 MB (3 processes)

An idle dev server turns out to be a near-flat line. A 30-second smoke run of
the same script recorded roughly 351 MB; ten minutes of sitting still brought it
to 355.7 MB — growth of about 1.3%. The server allocates what it needs at
startup and then stops.

Start editing and the picture changes. Same server, same three processes, same
ten minutes, but with five single-line edits to a mounted component: 564.8 MB.
That's 209 MB of growth, a 59% increase, from five edits.

Five edits is nothing. A real morning is a few hundred. The baseline doesn't
prove the server would fall over by lunchtime, but it does show where the
accumulation comes from, and it makes the shape of the problem legible. Idle
cost is fixed and modest. Session cost is what climbs. Any honest test of a
memory improvement has to look at the second one.

That also makes Scenario A a control. If 16.3 barely moves it, the release is
doing its work during the session, not at startup.

Now the builds. The 33% drop between the two is the expected cache effect, but
the breakdown is more interesting than the total. Compilation falls from 32.8s
to 18.4s — a 44% saving, and clearly where the cache earns its keep. Type
checking barely budges: 40s to 37.5s, under 7%.

Which means that by the second build, TypeScript accounts for 37.5 of 63.8
seconds. Nearly 60% of the wall-clock time goes to something the build cache
can't touch. Any further improvement to repeat builds has to come from the type
checker, not from caching more artifacts — and that's exactly what 16.3 claims
to address by letting next build type check with TypeScript 7.

So the baseline hands us a specific question to test, rather than a vague hope
that the upgrade feels faster.

Recompile latency

Worth recording separately, because it contradicted the assumption I started
with. Watching the raw dev log during a run, you see several multi-second
GET / 200 lines that look like slow recompiles. They aren't. They're the
one-time cold compile of the route and its proxy.

Event Recompile GET (wall) Steady GET
Initial cold load 12.1s
Edit 1 248 ms 59 ms
Edit 2 225 ms 55 ms
Edit 3 239 ms 57 ms
Edit 4 231 ms 55 ms
Edit 5 238 ms 57 ms

Median recompile after an edit: 238 ms. Median steady-state request: 57 ms. Fast
Refresh on this app is already quick, which leaves 16.3 very little room here.

One note, since it affects how you read these figures: Next.js logs route
compilation on a separate line from the request, so the GET ... in Xms number
can undercount badly. The cold load took 12.1s of wall-clock time while its
dev-log line reported 827 ms. Wall-clock is the honest number, and that's what
the table uses.

The upgrade

The upgrade itself was a non-event.

npm install --save-exact next@16.3.1 eslint-config-next@16.3.1
Enter fullscreen mode Exit fullscreen mode

Two cold builds afterward, both exit 0, no warnings, no type errors, 142 static
pages — same as before. No codemod, no API changes, nothing in the app touched.
On a 142-page production codebase, the framework upgrade was one command.
(npm audit reports seven pre-existing vulnerabilities, unchanged by the bump
and out of scope here.)

One side effect will show up in your git status and look like something went
wrong. It isn't. It's a feature, and an odd one.

Next.js now writes instructions to your AI agents

Running next dev on 16.3.x regenerates a nextjs-agent-rules block in
AGENTS.md at your repo root, from Next's own generate-agent-files.js. The
release notes call this "versioned docs for AI agents." In practice it's a short
message aimed past you, at whatever coding assistant reads the repo. It opens by
telling the agent that this isn't the Next.js it knows, that its training data
may be stale, and that it should read the version-matched docs bundled at
node_modules/next/dist/docs/ before writing any code.

That's a reasonable answer to a real problem. Any model trained before August
2026 has a confidently wrong picture of this release, and pointing it at docs
that ship with the installed version beats hoping it guesses well.

Practical note: the block regenerates on every next dev, so reverting it just
recreates the change. Commit it with the upgrade.

I'd flag one thing the block doesn't. A generated file that tells your tooling
how to behave is worth reading yourself, rather than committing on the strength
of its own explanation. Here the claim checks out against
generate-agent-files.js in the installed package — but that's the sort of
thing to verify rather than assume, especially as more frameworks start writing
into agent-readable files.

What actually broke

Nothing broke. Zero errors, zero warnings, zero required code changes.

That's a real result for a minor release, and I'd rather report it than pad this
section. It's also worth being precise about what it does and doesn't tell you.
This app has no custom webpack config, doesn't run middleware/proxy with
complex matchers, and doesn't lean on the next/image defaults in unusual ways.
A codebase that does may have a rougher time. What this run establishes is that
a straightforward production app upgrades cleanly — not that every app will.

Results: memory

Every scenario ran three times on each version, with the machine verified clean
before each of the twelve runs.

Idle — peak working set, MB

Version Run 1 Run 2 Run 3 Median
16.2.3 481.6 360.1 357.3 360.1
16.3.1 429.9 432.8 439.7 432.8

Active HMR — peak working set, MB

Version Run 1 Run 2 Run 3 Median
16.2.3 988.4 562.4 567.8 567.8
16.3.1 867.0 509.0 513.4 513.4

Read the medians, not the ranges. In three of the four groups, run 1 is a high
outlier, and the cause is known rather than mysterious: clearing .next between
phases failed on a locked pdf-parse temp directory, so the first run after
each version switch was working against a cache left by the other version. Runs
two and three land within 3–7 MB of each other, and the two versions' warm
clusters don't overlap. The spread is a systematic cold-start effect, not noise.

That gives two findings pointing in opposite directions.

Idle got worse: 360 MB to 433 MB, about 20% more. A dev server left running
with no requests costs more memory on 16.3.1 than on 16.2.3.

Active HMR got better: 568 MB to 513 MB, about 10% less. Under an actual
editing session, the direction flips.

The flip fits what the release targets — long development sessions, not idle
servers — and it justifies measuring both. An idle-only benchmark would have
concluded the release was a straight regression. An idle-only benchmark is also
what most people would have run.

But 10% isn't 90%. Vercel's "up to" is doing real work in that sentence.
Whatever workload produces a 90% reduction, it isn't a 142-page App Router app
edited five times over ten minutes on a Windows laptop. On this app the memory
change is small enough that you'd never notice it, and it arrives bundled with
an idle regression you'd never notice either.

Results: TypeScript 7

This is where the release earns its keep, and the baseline told us in advance
where to look. Type checking was 37.5 of 63.8 seconds on a warm build — the part
no amount of caching could reach.

Getting TypeScript 7 takes one command. It went GA in July 2026 and now ships as
the standard typescript package, and since Next resolves the checker from that
package, bumping it is the whole migration:

npm install -D typescript@7
Enter fullscreen mode Exit fullscreen mode

The type-check phase, across four cold builds:

Mode Build Compile TypeScript Errors
tsc 5.9.3 (16.2.3) #2 18.4s 37.5s 0
tsc 5.9.3 (16.3.1) #2 19.7s 29.3s 0
TS7, default #1 / #2 23.2s / 13.2s 4.3s / 4.4s 0
TS7, useTypeScriptCli: true #1 / #2 19.4s / 11.3s 3.0s / 3.8s 0

Roughly 33 seconds down to roughly 3.5. About eight times faster, and it holds
across every run. Turbopack compilation is unaffected — this is entirely the
type checker.

On total wall-clock, the warm build went from 63.8s on 16.2.3 to 21.2s on 16.3.1
with TS7 and the CLI flag. Two thirds of the build, gone, from a framework bump
and a dependency bump.

Two caveats matter more than the number.

Zero errors is a property of this project, not of TypeScript 7. This
codebase already sets strict: true, so TS7's stricter defaults surfaced
nothing new — a standalone tsc --noEmit across 270 files exited clean. A
codebase that isn't already strict should expect the opposite, and should budget
for cleanup rather than for a free eight-fold speedup.

The flag matters less than the docs imply. TypeScript 7 ships no JS compiler
API — lib/typescript.js is simply absent, leaving only the native binary
launchers. Reading Next's source, I expected the default typescript-api mode
to fail with E1467. It didn't: Next fell back to the TS7 CLI and built clean
without the flag. Setting experimental.useTypeScriptCli: true is marginally
faster, around 3s versus 4s, but it isn't the difference between working and not
working. I'm reporting this because my source-based prediction was wrong, and
the empirical result is the one that counts.

Should you upgrade?

Two cases.

If you're still on Next.js 15, the timeline decides for you. Version 15
reaches end of support on October 21, 2026 — no more security patches after
that. The 15 to 16 jump is a real major upgrade with breaking changes, including
the removal of synchronous access to params, searchParams, cookies(),
headers() and draftMode(). Budget real time for it, and start before the
deadline rather than during it.

If you're already on 16.1 or 16.2, upgrade — but do it for the type checker,
not the memory. The framework bump on its own buys you almost nothing measurable
and costs you nothing either: one command, zero errors, zero code changes. Do it
and move on. Then bump TypeScript to 7 the same afternoon, because that's where
two thirds of your build time is hiding. If your project isn't already strict,
do that part on its own branch and expect to spend real time on the errors it
surfaces. The speedup is worth it, but it isn't free for everyone.

Reproducing this

The measurement scripts, the raw JSON from all twelve runs, the full benchmark
tables and the complete upgrade log are here:
https://github.com/pfgiiu/artigo01

Two caveats for anyone repeating this. Clearing .next between runs has to
actually succeed — a locked temp file silently turned my first run of each group
into a cold-start outlier. And the recompile-latency figures in the raw data
aren't comparable across versions, because the baseline was captured cold and
the 16.3.1 run warm. I've left them out of the comparison above for that reason.

Numbers from a single app on a single machine are one data point, not a
benchmark suite. If you run the same procedure on your own codebase, I'd like to
see what you get.

Top comments (0)