We've covered Next.js 16, 16.1, and 16.2 in this series, and now Vercel has started publishing what's coming in 16.3. The lead feature is instant navigations: clicking a link on a server-rendered site responds immediately, the way it does in a single-page app.
This is a preview release. Everything below comes from running
16.3.0-preview.5in production on this site (preview.6 landed July 13), the three official posts (instant navigations, AI improvements, Turbopack), and a dig through the preview release notes on GitHub. APIs, flags, and behavior can change before stable. The upgrade notes and measurements are ours, and those won't.
We upgraded this site to the preview the week it dropped, built the full instant-navigations adoption on a branch, and merged none of it, because a fully static site already navigates instantly. The change that made this site feel faster was cutting 341KB of gzipped JavaScript.
What's new and shiny
The 16.3 line runs to six preview tags and 87 canaries as of mid July. We've put the biggest feature first.
Instant navigations: stream, cache, or block
Click a link on a typical server-rendered site and three things happen: you click, the browser waits for the server, and the next page appears. The wait in the middle can be 100ms or a full second depending on latency and how much work the server does, and while it lasts the page doesn't move. That gap is why apps like Linear went client-heavy in the first place: an SPA puts something on screen immediately, then fills in the data.
16.3 keeps the server-driven model and goes after the wait. With the new flags on, every route has to answer one question: what can the user see immediately when they navigate here? Three answers count.
Stream. Wrap slow data in <Suspense>. The user gets the page shell with loading states straight away, and content streams in as the server finishes. This is the SPA feel.
Cache. Mark data fetches with 'use cache'. The user gets previously cached UI, reused across requests, with no loading state at all.
Block. Some pages should wait. A blog post that flashes a skeleton for half a second is worse than a blank beat followed by the full article. Those routes opt out explicitly:
The enforcement is the clever bit. In development, a navigation that can't respond instantly is now an error, surfaced in a new Instant Insights panel. You fix it by streaming or caching, or you declare the blocking deliberate. Slow navigations stop being something users find in production and become something the framework flags at dev time.
There's a Playwright helper to hold that line in CI:
Assertions inside the instant() block have to pass without any network roundtrip. Existing tests check whether a page works; this checks whether it responds before the server does.
Partial prefetching: one shell per route
Until now, Next.js fired a prefetch request for every link in the viewport. A sidebar with twenty chat links meant twenty prefetch requests, all pointing at the same route. Vercel's own words: "Many of you told us that this looked ridiculous, and frankly, we agree."
16.3 prefetches one reusable shell per route instead: one for /chat/[id], one for /dashboard, cached for the whole session. Click any of the twenty chat links and the shell renders immediately while that chat's data streams in. If a shell isn't enough (say you want the chat header to arrive with real content), you escalate per link:
Even then, Next.js only prefetches down to what's synchronously available or marked 'use cache'. Prefetching used to be all-or-nothing, which was the main reason to turn it off entirely; now there's a middle setting.
Both behaviors sit behind flags in next.config.ts, and both are planned defaults in a future major:
The DevTools also grew a Navigation Inspector that pauses every navigation at the shell stage. It's the fastest way we found to see exactly what a user gets during that first paint.
No spam, only good stuff
Subscribe, for more hot takes
Turbopack stops holding everything in RAM
Dev memory eviction is now on by default: Turbopack moves cold compiler state out to the filesystem cache instead of holding everything in RAM. Vercel's own dashboard app dropped from 21.5GB to 2GB of dev-server memory, nextjs.org went from 4.6GB to 0.84GB, and a user in the feedback thread reported 20GB down to 5GB.
Builds get the same treatment behind experimental.turbopackFileSystemCacheForBuild: the persistent cache that already speeds up dev now applies to next build, so CI can carry .next between runs. Vercel's numbers again:
Two smaller Turbopack items: an experimental Rust port of the React Compiler (experimental.turbopackRustReactCompiler, 20-50% faster compilation in their tests, Turbopack-only), and Vite-compatible import.meta.glob support, which matters if you're migrating from Vite or you load content files by pattern.
Every benchmark above is Vercel measuring Vercel's own apps, plus one community report. Nobody independent has reproduced them yet, which is normal three weeks into a preview, but treat them as directional.
The framework now assumes an agent is in the loop
16.3 is the first release where the framework openly assumes an AI agent is part of the dev loop. next dev detects a coding agent and writes a managed block into your AGENTS.md, pointing the agent at version-matched docs bundled inside node_modules/next/dist/docs. The block keeps itself current across upgrades, and agentRules: false in the config turns it off. The docs went agent-readable too: append .md to any nextjs.org/docs URL and you get plain markdown. We shipped the same markdown-twin pattern on this site last year.
The repo now ships four first-party agent skills: one that adopts cache components for you, one that optimizes routes after adoption, one that adopts partial prefetching, and a dev-loop skill that connects your agent to the running dev server through a new /_next/mcp endpoint. That MCP server has a compile_route tool, so an agent can ask "does this route compile" in seconds instead of paying for a full next build to find out.
Dev errors got the same treatment. Every Instant Insights error carries a Copy prompt button that produces a ready-to-paste agent prompt, and the error docs pages are structured (patterns, trade-offs, gotchas) so an agent can act on them without a human translating. Having built our whole content pipeline around agents, we rate this as the sleeper feature of the release.
Node streams by default, edge runtime deprecated
The "better rendering under stress" tease from the announcement doesn't have its own post yet, but the release notes show what it is: rendering moved off Web Streams onto native Node.js streams, on by default since canary 38. If your app serves heavy server-rendered pages under load, this is where that improvement lives.
Also in the release notes: the edge runtime is deprecated as of this line. We hit it from the other side, since adopting cache components on our branch forced our OG image route off the edge runtime and onto Node anyway. If you export runtime = 'edge' anywhere, start planning the move now rather than at the next major.
Security patches now arrive monthly
Next.js security fixes used to ship whenever a fix was ready, which meant keeping up was a matter of watching the releases page. As of July 13 that's a schedule: one security release a month, with the first due July 20.
That first release patches 16.2 and 15.5. Two useful facts fall out of that. Stable 16.3 is still at least a few weeks away, and the 15.x line is still getting security fixes, so teams on the older major can upgrade on their own timetable. If you maintain a Next.js site, a monthly bump alongside the release cadence covers you, and it's a five-minute job now that the timing is predictable.
No help for us, mind. This site runs the 16.3 preview in production, which is exactly the sort of thing a security schedule exists to discourage, and we do it so we can write posts like this one. Every client project sits on patched stable, upgraded monthly, boring on purpose. Our own site takes the preview tags and finds out. If something in 16.3 was going to break a production deploy, we'd much rather it broke ours.
The grab bag, explained simply
The rest of the release notes are smaller features whose one-line changelog entries assume you already know what they mean, so here's the plain-English version of each.
Service workers, compiled properly. A service worker is a small script the browser keeps around for your site even when no tab is open. It's what makes offline mode, push notifications, and "install this site as an app" work. Next.js never had first-class support, so PWA setups leaned on community plugins and files copied into public/. Turbopack now compiles service workers like any other entry point and serves them from /_next/static, in both App and Pages router.
next/root-params. If your app starts with a dynamic segment like app/[lang]/, every component that needed the language took it as a prop, threaded down layer by layer. Now it's import { lang } from 'next/root-params' and await lang() from any server component, no prop threading. The getters come from your actual folder names, so the API surface is literally your directory structure. New in 16.3, server components only for now.
'use cache: private'. Regular 'use cache' output is shared between all visitors, which is exactly wrong for anything derived from cookies (a username in the header, a cart count). The private variant caches in that one visitor's browser instead, so personalized fragments can still take part in instant navigations without one user seeing another's data.
staleTimes. The router keeps in-memory copies of pages you've visited so going back is instant. staleTimes is the dial for how long those copies are trusted before refetching: static for fully prefetched pages (default 5 minutes) and dynamic for everything else (default 0, meaning always refetch). Still experimental, and it answers "why does clicking back sometimes refetch and sometimes not".
Graph CSS chunking. Bundlers have to decide how to split your CSS into files. Split too little and every page downloads all your styles; split per route and shared styles get duplicated or arrive in the wrong order, which is how you get the classic "this page looks different when I navigate to it versus when I refresh on it" bug. The new experimental.cssChunking: "graph" mode groups styles by which ones are actually used together across pages, instead of guessing from the route tree.
catchError and retry lost their prefixes. We flagged both in the 16.2 post as unstable_ experiments you shouldn't build on. In 16.3 the prefixes are gone: catchError handles server component errors without needing a whole error.tsx boundary, and retry inside an error boundary re-fetches the data rather than just clearing the error state and hoping.
A faster TypeScript, experimentally. Microsoft is rewriting the TypeScript compiler in Go (the project is called tsgo, and it typechecks roughly 10x faster). 16.3 adds an experimental flag to use it as the CLI's TypeScript backend. If your next build spends most of its time in typechecking, this is the line to watch.
Feature status at a glance
| What | Status in the 16.3 preview |
|---|---|
| Instant navigations (stream / cache / block) | Opt-in via cacheComponents: true |
| Partial prefetching (one shell per route) | Opt-in via partialPrefetching: true |
instant() Playwright test helper | New @next/playwright package |
| Turbopack dev memory eviction | On by default |
Persistent cache for next build | experimental.turbopackFileSystemCacheForBuild |
| Rust React Compiler | Experimental, Turbopack-only |
| Native Node.js streams rendering | On by default |
AGENTS.md auto-managed block | On when a coding agent is detected |
Docs as markdown (.md suffix, llms.txt) | Live on nextjs.org |
| Monthly security releases | First one July 20, patching 16.2 and 15.5 |
| Edge runtime | Deprecated |
Our upgrade experience
We bumped this site (a pnpm monorepo with three Next.js apps) to 16.3.0-preview.5 on June 30. The upgrade itself was uneventful: React 19.2.3 already satisfied the peer range, no code changes, production build green on the first try.
The one gotcha was monorepo-specific. A transitive peer dependency (a workflow package that peers on next >13) resolved its own copy of next@16.2.1 alongside our preview version. Two copies of Next.js in one type graph means check-types explodes with baffling errors about incompatible internal types. The fix is a version-specific pnpm override in the root package.json:
That collapses everything to one resolution. If you're in a monorepo and your typecheck breaks after the bump, check for a duplicate next before you blame the preview. Upgrade, override, green build: done in an afternoon.
Two weeks of running the preview in production since, and zero runtime issues. The risk in a preview is the APIs changing under you, not the code falling over, which is why the new flags stay off on this site until stable.
The PR we didn't merge
We did the full adoption on a branch: cacheComponents and partialPrefetching on site-wide, the disallowed segment configs (dynamic, revalidate, dynamicParams) removed from every page and route handler, blog rendering wrapped in a 'use cache' boundary, build-time non-determinism fixed (cache components forbids it), the OG image route moved off the edge runtime, plus a 19-test Playwright suite asserting instant navigation. Everything came back green.
Then we clicked around both deployments for a week and couldn't feel the difference.
The reason is boring: this site is already fully static. Every content page is prerendered at build time and served as files from a CDN. There's no server work for a loading shell to hide, so the navigation delay was never server-side. Cache components would have added a preview flag, a determinism constraint on the whole codebase, and soft-404 shell behavior we had to noindex around, in exchange for a difference we could not perceive with our own hands on the trackpad.
So the PR sits open, rebased and waiting for stable, and this site is still plain static files.
Spin up every variant, then click around
The method is the takeaway here, more than the verdict. We didn't debate rendering strategy in a doc. We had three deployments up at the same time:
- Full SSG (main, the control)
- Cache components with partial prefetching
- Cache components without partial prefetching
Then we navigated all three by hand, over an average office connection. We skipped the synthetic benchmarks because perceived navigation speed barely registers in metrics, and you can feel it within thirty seconds of using the site.
This used to be an expensive way to decide, a day or more of engineer time per variant. With coding agents it's close to free: describe the variant, let the agent build the branch, get a preview URL per PR. Two of our three PRs existed for a week purely so we could click around them and close them, and they settled an argument no benchmark could.
What actually made the site feel faster
While we were testing rendering variants we found the real problem, and it wasn't rendering strategy. It was payload.
Our MDX component map registered every component as an eager client reference on every content page. A decorative WebGL particle effect on team member cards was shipping three and @react-three/fiber (roughly 400KB) to every blog post. Four chart components used by exactly one post were shipping recharts (about 110KB) to all of them. Syntax highlighting ran client-side too, so react-refractor and its language grammars rode along on every page.
The fix was dynamic imports with ssr: false behind "use client" wrappers, which moves the heavy leaves into on-demand chunks outside the page manifest. Syntax highlighting moved to the server entirely, since it renders static markup with no hooks; only a small copy-button island stays client-side. One subtlety worth knowing: next/dynamic inside the RSC renderer does not help here, because the client-reference manifest still bundles the component. The dynamic import has to live inside a client component.
The measured result on content pages, from the merged PR:
| Metric | Before | After | Change |
|---|---|---|---|
| Eager first-load (gzip) | ~1196KB | ~855KB | -341KB (-29%) |
| three + @react-three/fiber | Every content page | On demand | ~400KB deferred |
| recharts | Every content page | On demand | ~110KB deferred |
| Syntax highlighting | Client bundle | Server-rendered | Grammars never ship |
| SSG classification | 51 SSG / 20 static | 51 SSG / 20 static | Unchanged |
This is the change we could feel. Less JavaScript means the browser parses, compiles, and hydrates less before the page responds to input. The click-to-movement gap that instant navigations targets got shorter on our site because there was less code standing between the click and the movement.
Both fixes are real: instant navigations is the right one for server-time gaps, and a smaller bundle is the right one for client-time gaps. Check which one you actually have before reaching for either.
Should you enable it?
Depends what you run.
App-like sites (dashboards, feeds, anything personalized or data-heavy): yes, this feature is for you. Your navigations do real server work, and a streaming shell is a much better answer than either blocking or client-fetching everything. Try the preview on a branch now, ship it at stable.
Content sites that can go fully static: you already have instant navigations. They're called static files. Spend the effort on your bundle instead; run a build analysis and find your equivalent of a 400KB particle effect. We'd bet money you have one.
Everything in between: the flags are opt-in per project but the discipline is per route. Enabling them forces every route to declare stream, cache, or block, which is a useful audit even if you end up blocking half your routes.
Quick comparison
| Next.js 16.2 | Next.js 16.3 (preview) | |
|---|---|---|
| Link prefetching | Every link in the viewport | One reusable shell per route (opt-in) |
| Slow navigations | Users find them in production | Dev-time errors in Instant Insights |
| Turbopack dev memory | Compiler state held in RAM | Cold state evicted to disk by default |
| Persistent Turbopack cache | Dev only | next build too (experimental) |
| Rendering | Web Streams | Native Node.js streams |
| React Compiler | JavaScript implementation | Experimental Rust port (Turbopack-only) |
catchError / retry | unstable_ prefixed | Stable |
| Agent tooling | AGENTS.md by hand | Managed block, four skills, /_next/mcp |
| Edge runtime | Available | Deprecated |
| Security releases | Whenever a fix was ready | Monthly, from July 20 |
How to upgrade
Three routes, depending on how much you want to adopt today.
Option 1: bump the version, leave the flags off. This is what we run in production:
For most apps that's the whole job. Ours needed the pnpm override above, and yours might need its equivalent if you're in a monorepo.
Option 2: let an agent do the adoption. Vercel published an agent skill for adopting cache components, and the migration is mostly mechanical: remove segment configs, add cache boundaries, fix determinism. It's a sensible thing to delegate, and it's how we'd redo our branch when stable lands.
Option 3: wait for stable. There's no date yet, and the July 20 security release targets 16.2 and 15.5, so stable 16.3 is at least a few weeks out. Waiting costs you nothing except time with the Instant Insights panel; the flags will still be opt-in when it lands.
If you do flip the flags, expect errors on day one. Each one is a route that's slow today without anyone having noticed, which is exactly what you flipped the flags to find out. The preview docs for instant navigations cover the details.
Current sharp edges
The feedback thread is the live list of what breaks right now. Worth knowing before you flip the flags:
- Static exports don't work with partial prefetching. If you ship
output: 'export', this whole feature set isn't for you yet. - Instant Insights false-flags parallel routes that never actually render, so you may get errors about slots you can't fix. Under review.
- Global styled-jsx styles can leak between routes when UI state is preserved across a navigation. Scope your overrides.
turbopackIgnorecomments don't silence dynamicpath.join()file access, which can trace entire directories into your standalone output and balloon deploy size. A fix is in flight.- Self-hosting on SST (v4.17+) with
cacheComponentscan break SSR entirely; the current workaround is turning the flag off. Vercel and AWS/OpenNext deploys are reported working. - Safari's dev tooling is flaky for Instant Insights; use Chrome or Firefox during development.
Two bugs you might have read about are already fixed in later canaries: the page title rendering as a URL path after navigation, and Windows access-denied errors on the Turbopack cache. Both fixes should be in the next preview tag if they aren't in yours.
Closing thoughts
Stable 16.3 has no date, the first monthly security release lands July 20, and our cache-components branch stays parked until there's a stable tag to rebase onto. We'll do the full writeup when that happens, including whether the parked PR finally merges.




