Home Next.js in 2026
Contents

Next.js in 2026

Next.js 16 finished a year-long retreat from hidden caching. Turbopack is the default bundler, '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 / slot

The 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 cacheComponents on 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)

  1. Latest stable is 16.3.6 (as of September 2026). 16.3.x is the Active LTS line; 15.5.x is Maintenance LTS.
  2. 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.
  3. 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.
  4. Turbopack is the default bundler. A custom webpack config now fails your build unless you opt out with --webpack or migrate.
  5. Async request APIs are fully async now. cookies(), headers(), draftMode(), params, searchParams — the synchronous compatibility period is over.
  6. middleware.ts is now proxy.ts. Rename the file and the exported function. The edge runtime is not supported in proxy.
  7. revalidateTag needs a second argument — a cache profile like 'max'. The single-argument form is deprecated and errors in TypeScript.
  8. New caching model is opt-in via cacheComponents: true. 'use cache' replaces unstable_cache, fetch cache options, dynamic, revalidate, and fetchCache.
  9. The dev server writes AGENTS.md with a version-matched docs pointer, so your coding agent stops using stale knowledge.
  10. 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 revert beats 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-md

The 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 latest

The codemod handles:

  • next.config.js → new turbopack configuration
  • next lint → ESLint CLI
  • middleware → proxy rename
  • removing unstable_ prefixes from stabilized APIs
  • removing experimental_ppr route 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@latest

2.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.
  • cacheLife profiles are 'seconds', 'minutes', 'hours', 'days', 'weeks', 'max'. You can define custom profiles or override the built-ins, including default.
  • use cache is in-memory by default. The old fetch Data Cache and unstable_cache persisted across deployments and serverless instances; use cache does not. Use use cache: remote or 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_cache to use cache and 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;
  // ...
}

useSearchParams always 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:

  1. Enable the flag, fix the segment configs. Convert dynamic, revalidate, fetchCache per the table above. Routes that already render instantly need nothing more.
  2. Opt out the routes that aren't ready. One codemod adds export const instant = false to every page, layout, and slot:
    npx @next/codemod@canary cache-components-instant-false ./app
    # in a src/ project: ./src/app
    Check the reported file count — a wrong path silently reports 0 ok.
  3. Fix synchronous IO. It cannot be deferred. new Date(), Date.now(), Math.random(), crypto.randomUUID() during prerender are build errors that instant = false does not clear. Wrap the part that needs them in <Suspense> and call connection() first, or move it to a Client Component.
  4. 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-adoption

3.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 useLayoutEffect cleanup.
  • Dialogs with initialisation effects won't re-fire if state was preserved — derive state from the URL instead.
  • Forms keep values and useActionState results after submission — reset in the submit handler.

Part 4 — Instant Navigations

This is 16.3's headline feature and it is opt-in, behind two flags:

// next.config.ts
const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

The problem it solves is the one Server Components created: less client JavaScript and fewer waterfalls, but navigations that feel slower than an SPA, because the app had to fetch before it could show anything. The fix is to make the shell available before the click.

4.1 The four tools

Tool What it does
Instant Insights A devtool that automatically surfaces any navigation that is not instant, with a prompt that teaches your agent the fix
Partial Prefetching Extract reusable loading shells from any route; per-link control over how much is prefetched
Better ISR An unlisted dynamic path serves an instant shell on first visit, then upgrades to the fully prerendered page in the background
Navigation Inspector Pause page loads and navigations at the shell to see exactly what the user would see

4.2 Streaming, caching, or blocking

Internally the model has three outcomes for a route's UI:

  • Stream — wrap dynamic UI in <Suspense> and it can be shipped ahead as a shell.
  • Cache — mark part of the UI 'use cache' so it can be prerendered and prefetched.
  • Block — set instant = false and accept the wait.

The default is Stream or Cache. Blocking is the exception you choose deliberately.

4.3 The regression test that keeps it fast

The failure mode this fixes is subtle: a page navigates instantly today, then someone adds a cookies() read to a shared header and the route de-opts to request time. Or a <Suspense> boundary moves in a refactor and part of the page starts blocking.

instant() from @next/playwright asserts what is visible without waiting for the network:

import { expect, test } from '@playwright/test';
import { instant } from '@next/playwright';

test('product title is available immediately', async ({ page }) => {
  await page.goto('/products/shoes');

  await instant(page, async () => {
    await page.click('a[href="/products/hats"]');
    await expect(page.locator('h1')).toContainText('Baseball Cap');
    await expect(page.getByText('Checking inventory...')).toBeVisible();
  });

  await expect(page.getByText('12 in stock')).toBeVisible();
});

The test fails whenever the instant UI changes, whatever the cause. If you have ever shipped a "why is this page slow now" bug, this is the fix: your navigation performance becomes a test, not a vibe.

4.4 Network resilience (experimental)

// next.config.ts
const nextConfig: NextConfig = {
  experimental: { useOffline: true },
};

With useOffline, a soft navigation, fetch, or Server Action that hits a dropped connection stays pending and retries on reconnect, rather than throwing. Because Partial Prefetching already caches a route's shell, a prefetched route still renders that shell offline and streams its data on reconnect.

'use client';
import { useOffline } from 'next/offline';

export function OfflineBanner() {
  const isOffline = useOffline();
  if (!isOffline) return null;
  return <div>You're offline. Retrying when you reconnect.</div>;
}

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 dev and next build use separate output directories, so they can run concurrently. next dev writes 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.md

That 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-adoption

next-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

  1. Branch and commit. git checkout -b chore/next-16.
  2. Check the floors. Node 20.9+, TS 5.1+.
  3. Set up agent docs. npx @next/codemod@canary agents-md.
  4. Run the codemod. npx next upgrade, then next-async-request-api if needed.
  5. Fix Turbopack. Remove --turbopack flags. If next build fails on a webpack config, decide: --webpack or migrate.
  6. Rename middleware. mv middleware.ts proxy.ts; rename the export; confirm you weren't relying on edge.
  7. Fix revalidateTag calls. Add a profile argument.
  8. Review next/image. Especially a self-hosted or proxied image source with query strings, and any quality prop not equal to 75.
  9. Add default.js to parallel route slots.
  10. Run next typegen and let TypeScript find the async-params sites.
  11. Build, deploy to preview, walk the interactive flows.
  12. 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 .md to any nextjs.org/docs URL for the Markdown source. If a link 404s, search <feature name> Next.js docs and navigate from nextjs.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

  1. Today: check Node 20.9+ and TS 5.1+. Branch.
  2. This week: run the codemods; fix Turbopack, async params, revalidateTag, proxy, and next/image.
  3. Next week: deploy to a preview, walk the interactive flows, take the security patch.
  4. Week 3: try reactCompiler: true and measure your build time.
  5. Week 4: enable cacheComponents on a branch. Use instant = false liberally, then convert one route at a time.
  6. Month 2: turn on partialPrefetching, add an instant() 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 via AGENTS.md; fewer prefetch requests through inlining; immutable static assets; catchError custom error boundaries with retry(); import.meta.glob; root params via next/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 the instant() helper from @next/playwright (same source).
  • Experimental: Rust React Compiler (reactCompiler + experimental.turbopackRustReactCompiler) with 34%/46% cold/warm improvements on v0; network resilience via experimental.useOffline and the useOffline hook (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/id for image metadata and sitemap; React 19.2; React Compiler stable; revalidateTag second argument; new updateTag and refresh; cacheLife/cacheTag stabilized; middleware → proxy (Node.js runtime only); PPR folded into cacheComponents; next/image default changes; concurrent dev/build with .next/dev; parallel-route default.js requirement; ESLint flat config; scroll-behavior override removed; removals of AMP, next lint, runtime config, devIndicators options, experimental.dynamicIO/useCache, unstable_rootParams (nextjs.org/docs/app/guides/upgrading/version-16).
  • Cache Components migration: use cache + cacheLife + cacheTag replacing route segment configs and fetch cache options; unstable_cache → use cache; noStore removal; updateTag vs revalidateTag semantics; use cache in-memory default vs prior durable caches; generateStaticParams must return at least one param; dynamicParams unsupported; <Suspense> requirements for runtime data; the instant = false opt-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 at node_modules/next/dist/docs/.
  • Codemods, then TypeScript. Let next typegen + tsc find the async-params sites rather than grepping.
  • Turbopack by default. Remove --turbopack flags; only reach for --webpack if a plugin forces it.
  • Cache Components later, incrementally. Enable the flag, run the instant-false codemod, then convert one route per sitting.
  • One invalidation trio per feature. cacheTag on the read, updateTag for 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 cacheComponents and partialPrefetching in 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