Next.js in 2026
'use cache' is the new caching primitive, and navigations are becoming instant. Here is what actually changed, and how to upgrade without breaking anything.The Big Picture
For three years the loudest complaint about Next.js was the same one: caching was magic and the magic was wrong. You would ship, something would be stale, and the fix was a route segment config you copy-pasted from a GitHub issue. Next.js 16 is the answer to that complaint, and the answer is not "better magic" — it is no magic.
The whole framework now has one caching primitive, 'use cache', that you place explicitly, and a default posture of dynamic by default. Nothing is cached unless you say so. That single change explains almost everything else in this release: the new cacheComponents model, Instant Navigations, the changes to revalidateTag, and the removal of a pile of older flags.
YOUR ROUTE (app/products/[slug]/page.tsx)
│
├── static shell — prerendered at build, served instantly
│ ┌──────────────────────────────────────────┐
│ │ <header>, layout, cached parts of page │
│ └──────────────────────────────────────────┘
│
└── dynamic holes — streamed at request time
┌──────────────────────────────────────────┐
│ <Suspense> around cookies()/headers() │
│ <Suspense> around searchParams │
└──────────────────────────────────────────┘
HOW DATA GETS CACHED — one directive, two axes
async function getProducts() {
'use cache' ← opt THIS function into caching
cacheLife('hours') ← how long it stays fresh
cacheTag('products') ← what invalidates it
return db.query(...)
}
HOW YOU INVALIDATE
updateTag('products') Server Action → user sees own write now
revalidateTag('products','max') anywhere else → stale-while-revalidate
refresh() re-render the client router
HOW YOU OPT OUT OF INSTANT-NAV VALIDATION (temporarily)
export const instant = false on a page / layout / slotThe single most important idea: use cache replaced a family of configs, and dynamic = 'force-dynamic' is now a no-op because everything is dynamic already. If you understand that one sentence, the migration is mostly deletion.
The analogy table
| Term | Plain-English analogy | Why it matters to you |
|---|---|---|
| Turbopack | A faster engine, now the only one in the car | Default for next dev and next build; your webpack config may block the build |
'use cache' |
A sticky note on a function: "keep this answer" | The one caching primitive; replaces unstable_cache, fetch cache options, and route segment configs |
cacheLife |
A sell-by date | How long a cached value stays fresh; built-in profiles or your own |
cacheTag |
A label on the jar | What you name to invalidate later |
updateTag |
"Change it and show me now" | Read-your-own-writes after a mutation; Server Actions only |
revalidateTag |
"Change it, I can wait" | Stale-while-revalidate; now requires a cache profile argument |
| Static shell | The shopfront that was built last week | Prerendered HTML served instantly, before any data arrives |
instant = false |
A "not ready" sticker | Lets one route opt out of instant-navigation validation while you migrate |
| Instant Navigations | SPA reflexes on a server-driven app | Prefetched shells so a click renders immediately |
| Partial Prefetching | Loading just the first screen of the next page | Per-link control over how much gets fetched ahead |
proxy.ts |
middleware.ts with a clearer name |
Renamed in 16; Node.js runtime only |
| Root params | A global the whole tree can read | [lang] accessible anywhere via next/root-params |
AGENTS.md |
A note to your coding agent, version-matched | next dev writes and maintains it; points at the bundled docs |
The one-sentence version: upgrade to 16.3 for real speed wins with zero code changes, then adopt
cacheComponentson your own schedule — because the new caching model is opt-in, and the old one still works while you migrate.
The 60-Second Version (TL;DR)
- Latest stable is 16.3.6 (as of September 2026).
16.3.xis the Active LTS line;15.5.xis Maintenance LTS. - Upgrade now, even if you change nothing else. 16.3 gives up to 90% less dev memory, faster builds, and ~22% more SSR throughput with zero application code changes.
- Upgrade to 16.3.6 or later for security. A scheduled release on September 30, 2026 (16.3.7 / 15.5.27) addresses nine vulnerabilities: one critical, two high, five medium, one low.
- Turbopack is the default bundler. A custom
webpackconfig now fails your build unless you opt out with--webpackor migrate. - Async request APIs are fully async now.
cookies(),headers(),draftMode(),params,searchParams— the synchronous compatibility period is over. middleware.tsis nowproxy.ts. Rename the file and the exported function. Theedgeruntime is not supported inproxy.revalidateTagneeds a second argument — a cache profile like'max'. The single-argument form is deprecated and errors in TypeScript.- New caching model is opt-in via
cacheComponents: true.'use cache'replacesunstable_cache,fetchcache options,dynamic,revalidate, andfetchCache. - The dev server writes
AGENTS.mdwith a version-matched docs pointer, so your coding agent stops using stale knowledge. - Node 20.9+, TypeScript 5.1+, Chrome/Edge/Firefox 111+, Safari 16.4+. Check these before anything else.
If you read nothing else, read Part 2 (upgrading) and Part 3 (the caching model).
Prerequisites
| Requirement | Minimum | How to check |
|---|---|---|
| Node.js | 20.9.0 (LTS) | node --version |
| TypeScript | 5.1.0 | npx tsc --version |
| Browsers | Chrome/Edge/Firefox 111+, Safari 16.4+ | Your support matrix |
| A Git working tree | — | Commit before upgrading; this touches a lot of files |
| An AI coding agent (recommended) | — | Next.js ships an upgrade prompt for it |
Before you start: on a solo SaaS, the upgrade is the risky part, not the features. Commit, branch, and upgrade on the branch. If a deploy breaks,
git revertbeats debugging in production.
Part 1 — What Actually Shipped: 16.0 → 16.3
You do not need to follow every release, but knowing the arc tells you what is stable and what is still moving.
| Release | Date | The headline |
|---|---|---|
| 16.0 | October 2025 | cacheComponents, use cache, Turbopack stable and default, React Compiler support, caching API overhaul, React 19.2 |
| 16.2 | March 2026 | ~400% faster next dev startup, ~50% faster rendering, stable Adapter API, AI improvements |
| 16.3 | August 2026 | Instant Navigations, memory eviction (up to 90% less dev RAM), build FS caching, TypeScript 7 support, root params |
1.1 The three things that matter in 16.3
Less memory in dev. Turbopack uses up to 90% less memory in next dev, via disk caching and memory eviction, both on by default. Vercel's own dashboard went from 21.5 GB to 2 GB after compiling 50 routes; nextjs.org went from 4,600 MB to 840 MB. If you have ever restarted your dev server because the laptop was hot, this is your release.
Faster builds. The disk cache now works for next build too. Cold vs cached: nextjs.org 21s → 9.2s (~2.3×), vercel.com/geist 30s → 5.5s (~5.5×).
Instant Navigations (opt-in). A suite of tools that makes a server-rendered app feel like an SPA: prefetched loading shells, per-link prefetch control, better ISR for unlisted paths, and a Playwright helper to stop a refactor from making navigation slow again.
These are vendor benchmarks run on Vercel's own properties. Your mileage will differ with app size and route count. The direction — less memory, faster repeat builds — is the reliable takeaway.
1.2 Smaller wins worth knowing
| Feature | What it gives you |
|---|---|
catchError |
Custom error boundaries that don't interfere with notFound()/redirect(), plus a retry() to refetch Server Components |
import.meta.glob |
Vite-compatible glob imports in Turbopack; HMR for Server Components reading local files |
| Root params | import { lang } from 'next/root-params' — read [lang] from any Server Component, no prop-drilling |
| TypeScript 7 | Bump your local typescript to ^7 for a much faster next build type check |
| Immutable static assets | Optionally reuse immutable assets across deploys (they can't suffer skew) |
| Fewer prefetch requests | Small prefetches are bundled; big shared segments stay separate and reusable |
1.3 Experimental, opt-in
| Flag | What it does |
|---|---|
reactCompiler: true + experimental.turbopackRustReactCompiler |
React Compiler through Rust inside Turbopack. On v0: 34% faster cold, 46% faster warm to a ready page |
experimental.useOffline + useOffline() hook |
Keeps a navigation or fetch pending through a network drop, retries on reconnect |
Part 2 — Upgrading: The Order That Actually Works
2.1 Step 0 — let your agent read the version-matched docs
Next.js 16.3's own answer to "my agent keeps using Next 13 patterns" is AGENTS.md. next dev writes and maintains a block that points at the docs bundled in your installed version:
npx @next/codemod@canary agents-mdThe managed block lands in AGENTS.md and looks like this:
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ
from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before
writing any code. Heed deprecation notices.
This block is written and re-added by `next dev`.
<!-- END:nextjs-agent-rules -->Why this matters more than it sounds: your coding agent's training data is older than your installed version. Pointing it at the bundled docs is the difference between a correct codemod-driven upgrade and a confident hallucination of a Next 14 API.
2.2 Step 1 — run the codemod
Next.js ships an upgrade codemod, and 16.0+ ships a next upgrade command:
# on 16.1+, the friendly path
npx next upgrade
# the codemod, explicit
npx @next/codemod@canary upgrade latestThe codemod handles:
next.config.js→ newturbopackconfigurationnext lint→ ESLint CLImiddleware→proxyrename- removing
unstable_prefixes from stabilized APIs - removing
experimental_pprroute segment config
It does not do everything. If you still use synchronous params, searchParams, cookies(), headers(), or draftMode(), run the async migration too:
npx @next/codemod@canary next-async-request-api .Or upgrade packages by hand:
npm install next@latest react@latest react-dom@latest
npm install -D @types/react@latest @types/react-dom@latest2.3 Step 2 — the breaking changes, in the order they'll bite
Turbopack is the default. If you have a custom webpack config, next build now fails on purpose. Three options:
// keep Turbopack, ignore the webpack config
{ "scripts": { "build": "next build --turbopack" } }// keep Webpack — opt out
{ "scripts": { "build": "next build --webpack" } }Or migrate the config to turbopack options. The experimental.turbopack key is now top-level turbopack.
Async request APIs are fully async. The Next 15 compatibility window is closed:
// Next 16 — params is a Promise
export default async function Page(props: PageProps<'/blog/[slug]'>) {
const { slug } = await props.params;
const query = await props.searchParams;
return <h1>{slug}</h1>;
}Run npx next typegen to generate PageProps, LayoutProps, and RouteContext helpers, and the migration becomes type-checked rather than guesswork.
middleware.ts → proxy.ts.
mv middleware.ts proxy.ts// rename the export too
export function proxy(request: Request) {}The proxy runtime is Node.js only and not configurable. The edge runtime is not supported there — if you need edge, stay on middleware for now. Flag names follow: skipMiddlewareUrlNormalize → skipProxyUrlNormalize.
revalidateTag takes two arguments.
// before
revalidateTag('posts');
// after
revalidateTag('posts', 'max');If you need immediate expiry, updateTag is the new Server-Action-only API (Part 3.4).
next/image defaults changed. Several at once:
| Setting | New default | If you need the old behavior |
|---|---|---|
minimumCacheTTL |
4 hours (was 60s) | images.minimumCacheTTL: 60 |
imageSizes |
16 removed |
Add it back explicitly |
qualities |
[75] only |
images.qualities: [50, 75, 100] |
maximumRedirects |
3 (was unlimited) | Raise it |
| Local images with query strings | Require images.localPatterns.search |
Add the pattern |
| Local IP optimization | Blocked | images.dangerouslyAllowLocalIP: true (understand the SSRF risk) |
Parallel routes need default.js. Every slot now requires one; builds fail without it.
// app/@modal/default.tsx
export default function Default() {
return null;
}Scroll behavior is no longer overridden. If you relied on Next forcing an instant scroll-to-top during navigation while you had scroll-behavior: smooth set, opt back in:
<html lang="en" data-scroll-behavior="smooth">Removed outright: AMP support, the next lint command (use ESLint or Biome directly; next build no longer lints), serverRuntimeConfig/publicRuntimeConfig (use env vars), some devIndicators options, experimental.dynamicIO and experimental.useCache (fold into cacheComponents), and unstable_rootParams.
2.4 Step 3 — verify
Per the official guide, verification is about the interactive states, not just a green build. Open the key flows, check the Next.js dev indicator, and read the browser and server logs. If a 16.3+ app is on Turbopack and the next-dev-loop skill is available to your agent, use it — it inspects the running app, makes an edit, and verifies the result.
If you are on Next.js 15 or earlier, work up through the version guides one major at a time: 14 → 15 → 16.
Part 3 — The New Caching Model, Properly
This is the part of Next.js 16 that changes how you think, not just what compiles. It is opt-in, per project, via one flag:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;Once enabled, dynamic, revalidate, and fetchCache route segment configs error if they're still exported. Here is what replaces each of them.
3.1 The one table you need
| You used to write | Write this instead |
|---|---|
export const dynamic = 'force-dynamic' |
Delete it. Everything is dynamic by default |
export const dynamic = 'force-static' |
Delete it, then add 'use cache' + cacheLife('max') where you want caching |
export const revalidate = 3600 |
'use cache' then cacheLife('hours') |
export const fetchCache = 'force-cache' |
'use cache' — fetches inside a cached scope are cached automatically |
fetch(url, { cache: 'force-cache', next: { revalidate, tags } }) |
'use cache' + cacheLife(...) + cacheTag(...) in a wrapper function |
unstable_cache(fn, keys, { tags, revalidate }) |
A plain async function with 'use cache', cacheTag, cacheLife |
unstable_noStore() / noStore() |
Delete it. Nothing is cached unless you opt in |
experimental.ppr = true / experimental_ppr |
Remove; cacheComponents is Partial Prerendering now |
runtime = 'edge' |
Not supported here. Use Proxy for edge-specific routing |
3.2 The shape of a cached function
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';
export async function getProducts() {
'use cache';
cacheLife('hours');
cacheTag('products');
return db.query('SELECT * FROM products');
}Three things to internalise:
- The cache key is derived from the arguments. No more key-parts array.
cacheLifeprofiles are'seconds','minutes','hours','days','weeks','max'. You can define custom profiles or override the built-ins, includingdefault.use cacheis in-memory by default. The oldfetchData Cache andunstable_cachepersisted across deployments and serverless instances;use cachedoes not. Useuse cache: remoteor a cache handler if you need durability. Even then, expect values to recompute after a new deployment.
This is the single biggest behavioural difference from the old model, and the easiest to miss. If you migrate
unstable_cachetouse cacheand assume persistence, your cache hit rate quietly drops on serverless.
3.3 Streaming instead of blocking
Under cacheComponents, runtime data access outside a <Suspense> boundary raises an insight. The fix is always the same: push the dynamic read down into its own component and wrap it.
import { cookies } from 'next/headers';
import { Suspense } from 'react';
// the page prerenders; only Dashboard streams at request time
export default function Page() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Dashboard />
</Suspense>
);
}
async function Dashboard() {
const theme = (await cookies()).get('theme')?.value;
// ...
}The same applies to searchParams:
export default function Page({ searchParams }: PageProps<'/'>) {
return (
<Suspense fallback={<p>Loading...</p>}>
<Results searchParams={searchParams} />
</Suspense>
);
}
async function Results({ searchParams }: Pick<PageProps<'/'>, 'searchParams'>) {
const { query } = await searchParams;
// ...
}
useSearchParamsalways needs a<Suspense>boundary — search params are only known at request time. Client router hooks (usePathname,useParams,useSelectedLayoutSegment(s)) suspend only when the path depends on dynamic params not yet known.
3.4 Invalidation: three functions, three feelings
'use server';
import { updateTag, revalidateTag, refresh } from 'next/cache';
// user made a change and must SEE it immediately (read-your-own-writes)
await db.users.update(userId, profile);
updateTag(`user-${userId}`);
// elsewhere (webhooks, route handlers) — stale-while-revalidate
revalidateTag('posts', 'max');
// after an action, re-render the client router
refresh();updateTag only works inside a Server Action; calling it elsewhere throws. revalidatePath is unchanged from the old model.
3.5 Migrating incrementally (the sane path)
You do not have to convert every route at once. The documented flow:
- Enable the flag, fix the segment configs. Convert
dynamic,revalidate,fetchCacheper the table above. Routes that already render instantly need nothing more. - Opt out the routes that aren't ready. One codemod adds
export const instant = falseto every page, layout, and slot:Check the reported file count — a wrong path silently reportsnpx @next/codemod@canary cache-components-instant-false ./app # in a src/ project: ./src/app0 ok. - Fix synchronous IO. It cannot be deferred.
new Date(),Date.now(),Math.random(),crypto.randomUUID()during prerender are build errors thatinstant = falsedoes not clear. Wrap the part that needs them in<Suspense>and callconnection()first, or move it to a Client Component. - Convert one route at a time. Remove
instant = false, resolve the insights, repeat.
There's also a skill that drives the whole migration with an agent, one feature at a time:
npx skills add vercel/next.js --skill next-cache-components-adoption3.6 The side effect nobody mentions
With Cache Components, Next.js preserves routes using React's <Activity> in "hidden" mode instead of unmounting them. useState values, form inputs, and scroll position survive navigation. Effects still clean up and re-run.
If your code relied on unmounting to reset state, you now need explicit resets:
- Dropdowns and popovers stay open when you navigate back — close them in a
useLayoutEffectcleanup. - Dialogs with initialisation effects won't re-fire if state was preserved — derive state from the URL instead.
- Forms keep values and
useActionStateresults after submission — reset in the submit handler.
Part 5 — Turbopack, and the Webpack Decision
Turbopack became the default in 16.0. The practical consequences:
If you have no custom webpack config: nothing to do. Your --turbopack flags are now redundant and should be removed from package.json:
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start" } }If you have a custom webpack config: next build fails deliberately, to stop you shipping a build that ignores it. Choose:
next build --turbopack— build with Turbopack, ignore the config.next build --webpack— opt out entirely.- Migrate the config to Turbopack options — the long-term answer.
Config moved. experimental.turbopack is now top-level:
// Next.js 16
const nextConfig: NextConfig = {
turbopack: {
// options
},
};Two migrations that catch people:
/* Webpack allowed the legacy tilde prefix */
@import '~bootstrap/dist/css/bootstrap.min.css';
/* Turbopack does not */
@import 'bootstrap/dist/css/bootstrap.min.css';If a client bundle imports a Node native module you'll see Module not found: Can't resolve 'fs'. The right fix is refactoring the import; the escape hatch is turbopack.resolveAlias:
turbopack: {
resolveAlias: {
fs: { browser: './empty.ts' }, // fix imports instead when you can
},
},The wins, restated: memory eviction during long dev sessions, and a persistent filesystem cache now covering next build. If a plugin adds a webpack option you didn't write, that's the usual cause of a sudden build failure.
Also new:
next devandnext builduse separate output directories, so they can run concurrently.next devwrites to.next/dev, and a lockfile prevents two dev or build instances on one project.
Part 6 — The AI-Agent Story (Why This Release Is Different)
Next.js has spent a year treating coding agents as first-class users, and 16.3 is where that becomes practical. Four things changed.
6.1 Versioned docs through AGENTS.md
Already covered in Part 2.1, but it belongs here too: the dev server writes and maintains a block pointing at node_modules/next/dist/docs/, so an agent reads docs that match your installed version. Vercel retired the earlier Skills that existed only to ferry current docs into an app.
6.2 Docs as Markdown
Append .md to any Next.js docs URL and you get the Markdown source. Try it on the page you're reading about:
https://nextjs.org/docs/app/getting-started/upgrading.mdThat makes docs scriptable — an agent can fetch exactly the page it needs, in a format it parses well, instead of scraping HTML.
6.3 First-party skills
Skills package multi-step workflows. For Next.js 16.3, the ones worth knowing:
npx skills add vercel/next.js --skill next-dev-loop
npx skills add vercel/next.js --skill next-cache-components-adoptionnext-dev-loop gives an agent a repeatable inspect → edit → verify cycle against the running dev server. next-cache-components-adoption drives the Cache Components migration one feature at a time.
6.4 The upgrade prompt
The official upgrade guide leads with an agent prompt rather than manual steps, which tells you where Vercel thinks the leverage is. The shape of it:
Upgrade this app to Next.js 16.
Before editing code, make sure AGENTS.md points at version-matched Next.js docs.
Then follow the Next.js 16 upgrade guide as the source of truth for the migration.
Use the codemod when you're ready to run the mechanical upgrade.
Briefly explain the upgrade plan before making broad changes.
Keep the migration scoped to the upgrade, inspect the diff, run the relevant checks,
and fix remaining breaking changes.The prompt's discipline is worth stealing even if you upgrade by hand: state the plan before editing, keep the change scoped, inspect the diff, verify the interactive states.
Part 7 — A Worked Upgrade on a Solo SaaS
The stack in this shelf: Next.js App Router on Vercel, Postgres, Stripe, a few LLM calls. Here's the order I'd do it in.
7.1 The checklist
- Branch and commit.
git checkout -b chore/next-16. - Check the floors. Node 20.9+, TS 5.1+.
- Set up agent docs.
npx @next/codemod@canary agents-md. - Run the codemod.
npx next upgrade, thennext-async-request-apiif needed. - Fix Turbopack. Remove
--turbopackflags. Ifnext buildfails on a webpack config, decide:--webpackor migrate. - Rename middleware.
mv middleware.ts proxy.ts; rename the export; confirm you weren't relying onedge. - Fix
revalidateTagcalls. Add a profile argument. - Review
next/image. Especially a self-hosted or proxied image source with query strings, and anyqualityprop not equal to 75. - Add
default.jsto parallel route slots. - Run
next typegenand let TypeScript find the async-params sites. - Build, deploy to preview, walk the interactive flows.
- Only then consider
cacheComponents, on its own branch.
7.2 Reading a real diff
A synchronous searchParams read before and after, since this is the change that touches the most files:
// before — Next 15 compatibility period
export default function Page({ searchParams }: { searchParams: { q?: string } }) {
const q = searchParams.q ?? '';
return <Results q={q} />;
}// after — Next 16, async, and Suspense-friendly
import { Suspense } from 'react';
export default function Page({ searchParams }: PageProps<'/'>) {
return (
<Suspense fallback={<p>Loading…</p>}>
<Results searchParams={searchParams} />
</Suspense>
);
}
async function Results({ searchParams }: Pick<PageProps<'/'>, 'searchParams'>) {
const { q = '' } = await searchParams;
return <ResultsList q={q} />;
}Note the shape isn't forced on you by the async change alone — you could just await at the top of the page. But doing it this way makes the route ready for cacheComponents later, because the dynamic read is already inside a boundary.
7.3 A webhook that invalidates, correctly
A Stripe webhook lands, and the billing page must show the new state:
// app/api/stripe/webhook/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(req: Request) {
const event = await verifyStripeSignature(req);
await recordEvent(event);
// Route Handlers can't call updateTag — use revalidateTag with a profile.
revalidateTag(`billing-${event.data.object.customer}`, 'max');
return Response.json({ ok: true });
}And the read side, tagged so that call finds it:
// app/lib/billing.ts
import { cacheLife, cacheTag } from 'next/cache';
export async function getBillingState(customerId: string) {
'use cache';
cacheLife('max');
cacheTag(`billing-${customerId}`);
return db.billing.findFirst({ where: { customerId } });
}And a user-facing mutation, where read-your-own-writes matters enough to use updateTag:
// app/actions.ts
'use server';
import { updateTag } from 'next/cache';
export async function renameReport(reportId: string, title: string) {
await db.reports.update(reportId, { title });
updateTag(`report-${reportId}`); // user sees the rename immediately
}That trio — cacheTag on the read, updateTag for the user's own mutation, revalidateTag(..., 'max') for the webhook — is the whole invalidation model in practice.
Cheat Sheet
# Upgrade
npx next upgrade # on 16.1+
npx @next/codemod@canary upgrade latest # explicit codemod
npx @next/codemod@canary next-async-request-api . # async params/APIs
npx @next/codemod@canary agents-md # versioned docs for agents
npx next typegen # PageProps / LayoutProps / RouteContext
# Incremental Cache Components adoption
npx @next/codemod@canary cache-components-instant-false ./app
npx skills add vercel/next.js --skill next-cache-components-adoption
npx skills add vercel/next.js --skill next-dev-loop// next.config.ts — the flags that matter
const nextConfig: NextConfig = {
// stable
reactCompiler: true,
turbopack: { /* top-level now, was experimental.turbopack */ },
// Next 16 caching + 16.3 instant navigations
cacheComponents: true,
partialPrefetching: true,
// experimental
experimental: {
turbopackRustReactCompiler: true,
useOffline: true,
},
};| Old | New |
|---|---|
export const dynamic = 'force-dynamic' |
delete |
export const revalidate = 3600 |
'use cache' + cacheLife('hours') |
export const fetchCache = 'force-cache' |
'use cache' |
unstable_cache(fn, keys, opts) |
async fn with 'use cache' + cacheTag + cacheLife |
fetch(url, { next: { revalidate, tags } }) |
'use cache' + cacheLife + cacheTag |
noStore() |
delete |
experimental.ppr / experimental_ppr |
cacheComponents: true |
revalidateTag('x') |
revalidateTag('x', 'max') or updateTag('x') |
middleware.ts |
proxy.ts, export proxy |
runtime = 'edge' in proxy |
not supported; use Proxy for edge routing |
middleware config flags |
proxy config flags |
cacheLife profile |
Roughly |
|---|---|
'seconds' |
seconds |
'minutes' |
minutes |
'hours' |
hours |
'days' |
days |
'weeks' |
weeks |
'max' |
as long as possible |
| Requirement | Minimum |
|---|---|
| Node.js | 20.9.0 LTS |
| TypeScript | 5.1.0 |
| Chrome / Edge / Firefox | 111+ |
| Safari | 16.4+ |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Build fails mentioning webpack config |
16 default is Turbopack | next build --turbopack to ignore, --webpack to opt out, or migrate the config |
Module not found: Can't resolve 'fs' |
Client bundle imports a Node native module | Refactor the import; turbopack.resolveAlias as a last resort |
Sass import with ~ fails |
Turbopack rejects the legacy tilde prefix | Drop the ~ |
params/searchParams typed as Promise but accessed sync |
Async request APIs are now fully async | await them; run next typegen and use PageProps |
revalidateTag TypeScript error |
New required profile argument | revalidateTag('tag', 'max') |
updateTag throws at runtime |
Called outside a Server Action | Use revalidateTag in Route Handlers/webhooks |
Build errors when enabling cacheComponents |
Unhandled uncached data outside <Suspense> |
Wrap in <Suspense>, or cache with 'use cache' |
instant = false did not fix the build |
Synchronous IO (new Date(), Math.random(), crypto.randomUUID()) |
Move it out of the prerendered shell: <Suspense> + connection(), or a Client Component |
dynamicParams export errors |
Not supported with cacheComponents |
Delete it; call notFound() instead of dynamicParams: false |
generateStaticParams returning [] errors |
Cache Components requires at least one param | Return one real param so the shell prerenders |
| Parallel route build failure | Missing default.js |
Add one returning null or calling notFound() |
| Dropdowns/dialogs/forms behave oddly after enabling Cache Components | UI state now persists via <Activity> |
Add explicit resets; derive state from the URL |
| Scroll-to-top stopped being instant | 16 no longer overrides scroll-behavior |
Add data-scroll-behavior="smooth" to <html> |
Cache hit rate dropped after migrating unstable_cache |
use cache is in-memory, not durable |
use cache: remote or a cache handler |
| Insights not visible | They only appear in the dev overlay / dev log / MCP get_errors |
Read the overlay or query the MCP tool; the HTTP response still returns 200 |
Video Library
YouTube search links only — release videos age faster than anything else here.
| Search | What you'll find |
|---|---|
| Next.js 16 upgrade guide | Migration walkthroughs |
| Next.js cacheComponents tutorial | The new caching model, explained |
| Next.js use cache directive | use cache, cacheLife, cacheTag in practice |
| Next.js Instant Navigations | Partial Prefetching and shells |
| Next.js Turbopack migration | Moving off webpack |
| Next.js middleware to proxy | The rename, and the edge question |
| Next.js React Compiler | Automatic memoization |
| Next.js AGENTS.md setup | Making your coding agent version-aware |
Written References & Docs
Vercel/Next.js domains first.
| Source | URL |
|---|---|
| Docs home | https://nextjs.org/docs |
| Docs index (machine-readable) | https://nextjs.org/docs/llms.txt |
| Upgrading overview | https://nextjs.org/docs/app/getting-started/upgrading |
| Upgrade guide — version 16 | https://nextjs.org/docs/app/guides/upgrading/version-16 |
| Migrating to Cache Components | https://nextjs.org/docs/app/guides/migrating-to-cache-components |
| Instant navigation guide | https://nextjs.org/docs/app/guides/instant-navigation |
| AI coding agents guide | https://nextjs.org/docs/app/guides/ai-agents |
cacheComponents config |
https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents |
use cache directive |
https://nextjs.org/docs/app/api-reference/directives/use-cache |
cacheLife |
https://nextjs.org/docs/app/api-reference/functions/cacheLife |
cacheTag |
https://nextjs.org/docs/app/api-reference/functions/cacheTag |
updateTag |
https://nextjs.org/docs/app/api-reference/functions/updateTag |
revalidateTag |
https://nextjs.org/docs/app/api-reference/functions/revalidateTag |
| Turbopack docs | https://nextjs.org/docs/app/api-reference/turbopack |
turbopackFileSystemCache |
https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopackFileSystemCache |
turbopackRustReactCompiler |
https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopackRustReactCompiler |
| Root params | https://nextjs.org/docs/app/api-reference/functions/next-root-params |
| Codemods | https://nextjs.org/docs/app/guides/upgrading/codemods |
| Next.js CLI | https://nextjs.org/docs/app/api-reference/cli/next |
| Release blog index | https://nextjs.org/blog |
| Blog — Next.js 16.3 | https://nextjs.org/blog/next-16-3 |
| Blog — 16.3 Instant Navigations | https://nextjs.org/blog/next-16-3-instant-navigations |
| Blog — 16.3 AI improvements | https://nextjs.org/blog/next-16-3-ai-improvements |
| Blog — 16.3 Turbopack | https://nextjs.org/blog/next-16-3-turbopack |
| Blog — Next.js 16 | https://nextjs.org/blog/next-16 |
| Blog — Next.js 16.2 | https://nextjs.org/blog/next-16-2 |
| Blog — building app-like experiences | https://nextjs.org/blog/building-app-like-experiences-with-nextjs-16-3 |
| Blog — making navigations instant in v0 | https://nextjs.org/blog/making-v0-navigations-instant |
| Blog — security release program | https://nextjs.org/blog/next-security-release-program |
| Blog — upcoming September security release | https://nextjs.org/blog/upcoming-nextjs-security-release-september-2026 |
| Blog — agentic future | https://nextjs.org/blog/agentic-future |
| Skills repo | https://github.com/vercel/next.js/tree/canary/skills |
| GitHub repo | https://github.com/vercel/next.js |
| GitHub discussions | https://github.com/vercel/next.js/discussions |
| Discord | https://nextjs.org/discord |
| TypeScript 7 announcement | https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/ |
| React 19.2 | https://react.dev/blog/2025/10/01/react-19-2 |
Verification tip: append
.mdto anynextjs.org/docsURL for the Markdown source. If a link 404s, search<feature name> Next.js docsand navigate fromnextjs.org.
Glossary
| Term | Plain-English definition |
|---|---|
| App Router | The file-system router in app/, built on React Server Components |
| Turbopack | The Rust bundler, default for dev and build since 16 |
| Webpack | The previous bundler; still available via --webpack |
| Route segment config | Exports like dynamic, revalidate, fetchCache — replaced by Cache Components |
'use cache' |
The directive that opts a function into caching |
cacheLife |
Defines how long a cached value stays fresh |
cacheTag |
Labels cached data so it can be invalidated |
updateTag |
Server-Action-only invalidation with read-your-own-writes semantics |
revalidateTag |
Stale-while-revalidate invalidation; requires a cache profile |
refresh |
Re-render the client router from a Server Action |
| Static shell | Prerendered HTML served before dynamic data arrives |
instant = false |
Opt a route out of instant-navigation validation while migrating |
| Instant Insights | Devtool listing navigations that aren't instant |
| Partial Prefetching | Per-link control over how much of the next page is fetched ahead |
| Partial Prerendering (PPR) | Subsumed by cacheComponents in 16 |
proxy.ts |
The renamed middleware.ts; Node.js runtime, not configurable |
| Root params | Params defined above the root layout, readable from any Server Component |
AGENTS.md |
File next dev writes with version-matched docs pointers |
| Synchronous IO | new Date(), Math.random(), crypto.randomUUID() — build errors during prerender |
<Activity> |
React component Next.js uses to preserve route state in hidden mode |
| Active LTS | 16.3.x — the current recommended line |
| Maintenance LTS | 15.5.x — security fixes only |
FAQ & Next Steps
Should I upgrade right now? For security and speed, yes — with one caveat. A scheduled release on September 30, 2026 addresses nine vulnerabilities (one critical). Upgrade to at least 16.3.6 now, and plan to take 16.3.7 when it lands.
Do I have to adopt Cache Components? No. It's opt-in. The old caching model still works, and you can migrate route by route at your own pace.
Is use cache a drop-in for unstable_cache? Not quite. The ergonomics are better and the key is automatic, but persistence differs: use cache is in-memory by default and doesn't survive instance teardown. Use use cache: remote or a cache handler for durability.
What's the fastest win? Upgrade to 16.3 and change nothing. Up to 90% less dev memory and faster repeat builds, with zero app changes.
Turbopack or Webpack? Turbopack, unless a dependency forces otherwise. Migrate the config rather than staying on --webpack indefinitely.
Did middleware really get renamed? Yes — to proxy, and the function export too. The edge runtime is not supported there, so if you need edge, stay on middleware for now.
My agent keeps writing Next 14 code. Run the agents-md codemod and confirm AGENTS.md points at node_modules/next/dist/docs/.
What breaks the build that instant = false won't fix? Synchronous IO — new Date(), Date.now(), Math.random(), crypto.randomUUID() during prerender. Those must move out of the static shell.
Where do the perf numbers come from? Vercel's release posts, measured on its own properties. Re-measure on your app.
Next steps, in order
- Today: check Node 20.9+ and TS 5.1+. Branch.
- This week: run the codemods; fix Turbopack, async params,
revalidateTag,proxy, andnext/image. - Next week: deploy to a preview, walk the interactive flows, take the security patch.
- Week 3: try
reactCompiler: trueand measure your build time. - Week 4: enable
cacheComponentson a branch. Useinstant = falseliberally, then convert one route at a time. - Month 2: turn on
partialPrefetching, add aninstant()Playwright test for your busiest navigation.
Verification Note
Verified as of September 2026 from Vercel/Next.js primary sources:
- Versions: latest stable 16.3.6; Active LTS 16.3.x, Maintenance LTS 15.5.x. A scheduled security release on September 30, 2026 (16.3.7 / 15.5.27) addresses nine vulnerabilities: one critical, two high, five medium, one low (
nextjs.org/blog). - Next.js 16.3 features: up to 90% less dev memory (memory eviction + dev disk caching, on by default); build filesystem caching with reported 2.3×–5.5× faster repeat builds; TypeScript 7 support via
typescript@^7; ~22% more requests under load from native Node.js streams; versioned docs viaAGENTS.md; fewer prefetch requests through inlining; immutable static assets;catchErrorcustom error boundaries withretry();import.meta.glob; root params vianext/root-params(nextjs.org/blog/next-16-3). - Instant Navigations: opt-in via
cacheComponents: true+partialPrefetching: true; Instant Insights, Partial Prefetching, better ISR for unlisted dynamic paths, Navigation Inspector, and theinstant()helper from@next/playwright(same source). - Experimental: Rust React Compiler (
reactCompiler+experimental.turbopackRustReactCompiler) with 34%/46% cold/warm improvements on v0; network resilience viaexperimental.useOfflineand theuseOfflinehook (same source). - Next.js 16 upgrade requirements and breaking changes: Node.js 20.9+, TypeScript 5.1+, Chrome/Edge/Firefox 111+, Safari 16.4+; Turbopack default with webpack-config build failure; fully-async request APIs; async
params/idfor image metadata andsitemap; React 19.2; React Compiler stable;revalidateTagsecond argument; newupdateTagandrefresh;cacheLife/cacheTagstabilized;middleware→proxy(Node.js runtime only); PPR folded intocacheComponents;next/imagedefault changes; concurrent dev/build with.next/dev; parallel-routedefault.jsrequirement; ESLint flat config; scroll-behavior override removed; removals of AMP,next lint, runtime config,devIndicatorsoptions,experimental.dynamicIO/useCache,unstable_rootParams(nextjs.org/docs/app/guides/upgrading/version-16). - Cache Components migration:
use cache+cacheLife+cacheTagreplacing route segment configs andfetchcache options;unstable_cache→use cache;noStoreremoval;updateTagvsrevalidateTagsemantics;use cachein-memory default vs prior durable caches;generateStaticParamsmust return at least one param;dynamicParamsunsupported;<Suspense>requirements for runtime data; theinstant = falseopt-out and its limits;<Activity>UI-state preservation behaviour (nextjs.org/docs/app/guides/migrating-to-cache-components).
Vendor claims, not independently verified: all performance figures (memory, build speed, SSR throughput, React Compiler gains) come from Vercel's release posts and are measured on its own properties. The direction is credible; the multipliers are marketing until you reproduce them.
Changes fast: the exact patch version and the September 30 security release contents; the supported-client list for skills; experimental flag names; and whether any given feature has graduated from experimental. Re-check nextjs.org/blog and the version guide before depending on a specific behaviour.
Not security advice. If you handle regulated data, read the security release notes and the Next.js security documentation directly, and patch on the vendor's schedule rather than this summary's.
Your Setup Notes (Mac · VS Code · opencode)
How I'd sequence this on my own stack.
| In my workspace | Verdict |
|---|---|
| Next.js App Router on Vercel | Upgrade path is well-trodden; the codemods do most of it |
| opencode as the coding agent | Run agents-md first so it stops writing Next 14 code |
| Postgres + Stripe + a couple LLM calls | The webhook invalidation pattern in Part 7.3 maps directly |
| A single deploy target (Vercel) | Turbopack migration is low risk; no custom webpack config to fight |
| No Playwright tests today | The instant() helper is the first test I'd add, on the busiest nav |
Recommended stack for this upgrade:
- Do it on a branch.
chore/next-16. The codemod touches many files. - Agent docs first.
npx @next/codemod@canary agents-md, then confirm the block points atnode_modules/next/dist/docs/. - Codemods, then TypeScript. Let
next typegen+tscfind the async-params sites rather than grepping. - Turbopack by default. Remove
--turbopackflags; only reach for--webpackif a plugin forces it. - Cache Components later, incrementally. Enable the flag, run the
instant-falsecodemod, then convert one route per sitting. - One invalidation trio per feature.
cacheTagon the read,updateTagfor the user's own mutation,revalidateTag(..., 'max')for webhooks. - One
instant()test. The busiest navigation, asserted against network timing. - What I would not do: enable
cacheComponentsandpartialPrefetchingin the same PR as the version bump. Two variables at once is how you get a three-day debugging session.
Smoke test for the first session, in order:
node --version # want v20.9.0 or later
git checkout -b chore/next-16
npx @next/codemod@canary agents-md
npx next upgrade
npm run build # expect failures; fix Turbopack, async params, revalidateTag