Shipping a Next.js app is easy; running one for years is a different skill. Between March 2025 and August 2026 the framework and its React Server Components layer produced an authorisation bypass that ignored middleware entirely, a CVSS 10.0 unauthenticated remote code execution in the RSC protocol, a run of follow-up denial-of-service and source-exposure bugs, and critical fixes in the image optimiser's native dependencies. Teams that had layered defences, a patch process, and observability shrugged; teams whose only auth check lived in middleware, or who ran a year-old minor, did not. This post covers authentication patterns that survive a bypass, the incidents themselves and what each teaches, next/image and next/font in production, bundle analysis, self-hosting with output: 'standalone' and Docker versus a managed platform, instrumentation.ts with OpenTelemetry, and environment-variable leaks. It ends with the planned extensions of this series.

Series map: 00 App Router Mental Model → 01 Data Fetching, Caching & Server Actions → 02 Rendering Strategies & Runtimes → 03 Production Next.js (this post) (future: see the extensions table below).


Learning Outcome

  • Design authentication in layers (Proxy, Data Access Layer, per-page and per-action checks) so that no single bypass exposes data.
  • Explain CVE-2025-29927 and CVE-2025-55182 (React2Shell): mechanism, affected versions, fixed versions, and the lessons.
  • Run a patch process that matches the Next.js LTS policy.
  • Configure next/image safely for Next.js 16, and use next/font without layout shift or third-party requests.
  • Find client-bundle bloat with the bundle analyzer and fix it at the server/client boundary.
  • Self-host with output: 'standalone' in Docker, including shared caching, the Server Actions key, version skew and graceful shutdown.
  • Instrument the server with instrumentation.ts, onRequestError and OpenTelemetry.
  • Prevent secret leaks through NEXT_PUBLIC_, next.config env, props and action return values.

Chapter 1 — Authentication in layers

Terminology

  • Authentication — who is the caller? (a valid session)
  • Authorisation — may this caller do this to this resource? (ownership, role)
  • Optimistic check — a cheap check on the presence of a session cookie, without a database or full verification; good for redirects, never enough for data.
  • Secure check — full session verification plus an authorisation decision, next to the data access.
  • IDOR — Insecure Direct Object Reference: the caller changes an id and gets someone else's record because only authentication was checked.

Where each check belongs

Layer Check Purpose Enough on its own?
proxy.ts Optimistic (cookie present?) Redirect logged-out users early; cheap No: bypassable, and skips any path its matcher excludes
Layout Avoid auth logic here A shared layout is not re-rendered on navigation between its children (partial rendering) No
Page / Server Component Secure, via the DAL Decide what UI to render For that page's reads
Server Action Secure, inside the action Actions are public POST endpoints For that mutation
Route Handler Secure, inside the handler Public HTTP endpoint For that endpoint
Data Access Layer Secure + authorisation per query The one place data is read or written The real boundary

The DAL from Post 01, extended with a verified session that is memoised per request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// data/session.ts
import 'server-only'
import { cache } from 'react'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { decrypt } from '@/lib/session-crypto' // verifies signature and expiry

export const verifySession = cache(async () => {
const token = (await cookies()).get('session')?.value
const payload = token ? await decrypt(token) : null
if (!payload?.userId) redirect('/login')
return { userId: String(payload.userId), role: String(payload.role) }
})

Every DAL function starts with await verifySession() and scopes its query to the caller (where: { id, ownerId: userId }). A page, an action and a Route Handler then get the same guarantees without remembering to write them.

Sessions

  • Stateless (a signed and encrypted token in a cookie): no session store, but revocation before expiry needs extra machinery (short lifetimes, a deny-list).
  • Database sessions (an opaque id in the cookie, state server-side): revocable instantly, one lookup per request (cheap with React.cache).
  • Either way the cookie is HttpOnly, Secure, SameSite=Lax (or Strict), with a sensible Max-Age, and is set or deleted only in Server Actions or Route Handlers: setting cookies during render is not allowed.

Prefer a maintained auth library over hand-rolled crypto; the layering above applies whichever library you choose.

Auth with Cache Components

Under cacheComponents (Post 02), per-user data must never sit in a 'use cache' scope keyed only by public inputs. Read the session outside the cached scope and pass the user id in as an argument, so it becomes part of the key; or keep per-user parts as uncached dynamic holes inside Suspense. Since 15.1, forbidden() and unauthorized() (behind experimental.authInterrupts, still experimental as of 16.3) render forbidden.tsx / unauthorized.tsx with 403 / 401 status, as long as they run before streaming starts.

Common pitfall: "the dashboard layout checks the session, so all dashboard pages are safe." The layout's check runs on the first load; on soft navigation between child pages the layout is not re-rendered, and the child pages' actions are separate endpoints. Check in the DAL.


Chapter 2 — The incidents, and what they teach

CVE-2025-29927: middleware bypass (March 2025)

Next.js used an internal header, x-middleware-subrequest, to stop middleware from recursing into itself. The server trusted that header on incoming requests, so an attacker who sent it with the right value made the server skip middleware entirely: authentication redirects, authorisation checks, security headers, everything.

  • Severity: critical, CVSS 9.1.
  • Affected: self-hosted apps using next start (including output: 'standalone'). Vercel, Netlify, Cloudflare Workers and static exports were not affected (Vercel's routing is decoupled from the Next.js server; exports have no server).
  • Fixed in: 15.2.3, 14.2.25, and backports 13.5.9 and 12.3.5.
  • Timeline: reported late February 2025, patched March 17–18, disclosed March 21.
  • Workaround: strip x-middleware-subrequest at the load balancer or reverse proxy before it reaches Next.js.

Lesson: an app whose only authorisation lived in middleware had none. Apps that re-checked in the DAL lost a redirect, not their data.

CVE-2025-55182 "React2Shell": RSC remote code execution (December 2025)

A deserialisation flaw in how React decodes payloads sent to Server Function endpoints let a crafted HTTP request execute code on the server, without authentication.

  • Severity: critical, CVSS 10.0. Reported November 29, disclosed December 3, 2025 (reporter: Lachlan Davidson).
  • Affected React packages: react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack at 19.0, 19.1.0, 19.1.1 and 19.2.0; fixed in 19.0.1, 19.1.2 and 19.2.1. Frameworks affected included Next.js, React Router, Waku, @parcel/rsc, @vitejs/plugin-rsc and rwsdk.
  • Not only apps with actions: React's advisory states that an app without any Server Functions of its own may still be vulnerable if it supports React Server Components.
  • Next.js impact (tracked as CVE-2025-66478): App Router apps on 15.x, 16.x, and 14.3.0-canary.77 or later canaries. Next.js 13.x, 14.x stable, the Pages Router and the Edge runtime were not affected.
  • Next.js fixed versions: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7. No workaround; upgrade was required. npx fix-react2shell-next performed the version bumps.
  • Exploitation: exploited in the wild within days; AWS reported China-nexus threat groups using it.
  • Secrets: Vercel advised that any app online and unpatched as of December 4, 2025, 1:00 PM PT should rotate its secrets, most critical first.

The follow-ups (December 2025 – January 2026)

Fixing a deserialiser invites a closer look at it:

CVE Type CVSS Note
CVE-2025-55184 Denial of service (infinite loop on a crafted request to a Server Function endpoint) 7.5 Disclosed Dec 11, 2025
CVE-2025-55183 Source code exposure of Server Functions that stringify their arguments 5.3 Hardcoded secrets in source could leak; runtime process.env values were not affected
CVE-2025-67779 Denial of service 7.5 The first DoS fix was incomplete
CVE-2026-23864 Denial of service (crashes, out-of-memory, CPU) 7.5 Disclosed Jan 26, 2026

Only 19.0.4, 19.1.5 and 19.2.4 (and later) of the react-server-dom-* packages are safe from all of them. None of these enabled remote code execution.

2026: a monthly rhythm

Next.js security releases became regular events in 2026, on both supported lines (16.x Active LTS, 15.5.x Maintenance LTS). The July 2026 release (16.2.11 / 15.5.21) fixed 4 high and 5 medium issues. The August 2026 release (16.3.3 / 15.5.24, August 25) fixed two critical ones: unauthenticated remote code execution in the Image Optimization API through the libheif library used by sharp when optimising an attacker-controlled AVIF image (the patch disables AVIF optimisation until the upstream fix propagates), and an unauthenticated remote code execution on Windows-hosted servers running both routers without Cache Components (CVE-2026-75604).

The timeline, with fixed versions:

Timeline of Next.js and React Server Components security incidents from March 2025 to August 2026 with severity and the first fixed versions for each

Lessons that transfer

Lesson Practice
Defence in depth Auth in the DAL; Proxy only as an optimisation
Patch speed is a security control Track the release line; automated dependency PRs; a documented "patch within 24 h" path for critical advisories
Know the support window Next.js LTS: the current major is Active LTS until the next major; the previous major gets Maintenance LTS (critical and security fixes) for 2 years from its release. As of 2026: 16.x Active, 15.x Maintenance. 14.x is outside the policy.
Rotate after exposure A remote code execution window means every secret the process could read is suspect
Shrink the attack surface Disable features you do not use: images.unoptimized if you do not need the optimiser, no unused Route Handlers, no 'use server' exports nobody calls
A WAF rule is a stopgap Vendors shipped rules within hours for React2Shell; the fix was still the upgrade
Keep secrets out of source CVE-2025-55183 exposed function source, not process.env

This blog runs Next.js 14.2 as a static export: no server process, so none of the server-side issues above apply. It still sits outside the LTS window, which matters the day it gains a server.


Chapter 3 — next/image and next/font

next/image

<Image> wraps <img> with: lazy loading by default, a srcset generated from width lists, format negotiation through the Accept header (formats, default ['image/webp']; add 'image/avif' to prefer AVIF), and required width/height (or fill) so the browser reserves space and avoids layout shift.

1
2
3
4
5
6
7
<Image
src={hero} // static import: hashed, cached forever (immutable)
alt="Dashboard overview"
sizes="(max-width: 768px) 100vw, 50vw" // without sizes the srcset only offers 1x/2x
preload // 16: replaces the deprecated `priority`
/>

Default width lists: deviceSizes [640, 750, 828, 1080, 1200, 1920, 2048, 3840] and imageSizes [32, 48, 64, 96, 128, 256, 384].

Next.js 16 changed several defaults, mostly for security:

Setting Before 16 16
qualities Any 1–100 [75]; other quality values are coerced to the nearest allowed value
minimumCacheTTL 60 s 14,400 s (4 h)
imageSizes included 16 16 removed
priority prop Supported Deprecated in favour of preload
Local src with a query string Allowed Requires images.localPatterns (prevents enumeration)
Optimising images from local IPs Allowed Blocked unless dangerouslyAllowLocalIP
Redirects when fetching remote images Unlimited maximumRedirects 3
images.domains Supported Deprecated: use remotePatterns

Production notes:

  • The optimiser is a server endpoint that fetches and decodes untrusted images: it is SSRF, CPU and native-library attack surface (the August 2026 AVIF issue). Restrict remotePatterns to exact hosts and paths.
  • dangerouslyAllowSVG serves SVG through the optimiser; pair it with a contentSecurityPolicy for images. Since 15, contentDispositionType defaults to attachment.
  • There is no API to invalidate optimised images; they live under <distDir>/cache/images until the TTL runs out. Change the src to bust them.
  • On glibc Linux, sharp may need allocator tuning to avoid excessive memory; Next.js 15+ uses sharp automatically with next start and standalone output.
  • A static export has no optimiser: set images.unoptimized (this blog does) or supply a custom loader that points at an image CDN.

next/font

next/font downloads Google Font files (or uses your local files) at build time and serves them with your static assets, so the browser never contacts Google. It generates a fallback @font-face with adjusted metrics (adjustFontFallback, default true for Google fonts and 'Arial' for local fonts) to reduce layout shift while the real font loads, uses display: 'swap' by default, and preloads the font on the routes that use it (preload default true; the subsets you list are what gets preloaded).

1
2
3
4
5
6
7
8
// app/fonts.ts
import { Inter, JetBrains_Mono } from 'next/font/google'
export const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
export const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono' })

// app/layout.tsx
// <html lang="en" className={`${inter.variable} ${mono.variable}`}>

Call each font loader once in a definitions module and import the object where needed; every call creates a separate hosted instance. A font used in a page is preloaded only on that route, one used in the root layout on all routes.


Chapter 4 — Bundle analysis

Tools

  • next build output lists every route with its size, the first-load JS, and the rendering mode symbol (Post 02).
  • Next.js Bundle Analyzer (experimental since 16.1, Turbopack): next experimental-analyze opens an interactive view of client and server bundles, filterable by route, showing the full import chain for any module, including across the server-to-client boundary and dynamic imports.
  • @next/bundle-analyzer: the webpack-era plugin, still useful for projects building with --webpack.

What to look for

Finding Usual cause Fix
A large library in first-load JS Imported from a 'use client' file for one small use Do the work in a Server Component; pass the result as a prop
The same library in many route chunks Barrel file (index.ts re-exporting everything) Import from the specific module; experimental.optimizePackageImports for known packages
A heavy widget nobody sees above the fold Eager import next/dynamic / import() behind interaction or visibility
A server-only package in the client graph Accidental import through shared utils import 'server-only' in the module; split the file
Huge RSC payload rather than JS Large props to Client Components Pass DTOs, not rows (Post 00)
Slow server cold starts Enormous server bundle Check the server view of the analyzer; serverExternalPackages for packages that should stay unbundled

Trick: measure before and after with the route table from next build, not with dev-mode network numbers. Development bundles are unminified and compiled on demand.


Chapter 5 — Self-hosting vs a managed platform

Standalone output

output: 'standalone' uses output file tracing (@vercel/nft) to copy only the files each route needs, including the needed parts of node_modules, into .next/standalone, with a minimal server.js that replaces next start. It does not copy public/ or .next/static, which are expected to go to a CDN; copy them in if the server should serve them. PORT and HOSTNAME set the listen address. In a monorepo, set outputFileTracingRoot so files outside the app folder are traced.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Dockerfile (multi-stage)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# NEXT_PUBLIC_* values are inlined here, at build time
RUN npm run build

FROM node:22-alpine AS run
WORKDIR /app

ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/.next/standalone ./
COPY --from=build --chown=app:app /app/.next/static ./.next/static
COPY --from=build --chown=app:app /app/public ./public
USER app
EXPOSE 3000
CMD ["node", "server.js"]

Next.js 16 needs Node.js 20.9 or later; any current LTS base image satisfies that.

The multi-instance checklist

One container works out of the box. Two or more need all of these:

Concern Default Multi-instance fix
ISR / fetch cache Per-instance disk + 50 MB memory cacheHandler pointing at shared storage; cacheMaxMemorySize: 0
'use cache' entries Per-instance memory LRU cacheHandlers (for 'use cache: remote') backed by shared storage
Tag invalidation Only on the instance that called it Implement refreshTags() in the handler to sync tag state before each request
Server Actions closures New key per build Same NEXT_SERVER_ACTIONS_ENCRYPTION_KEY everywhere
Build identity Random build ID One image for all environments, or generateBuildId
Version skew in rolling deploys Old clients hit new servers deploymentId: assets get ?dpl=, navigations send x-deployment-id, a mismatch forces a hard reload
Streaming Buffered by Nginx X-Accel-Buffering: no; check the load balancer
after() work Lost on abrupt kill Send SIGTERM/SIGINT and allow a 10–30 s drain
Bad clients Hit Node.js directly A reverse proxy for slow connections, payload limits, rate limits

Vercel, adapters, or your own

Managed (Vercel) Self-hosted Node.js / Docker
ISR, tag invalidation, 'use cache: remote' Built in Your cache handler
Skew protection Built in deploymentId + your deploy strategy
Image optimisation Platform service sharp in your container
Proxy bypass (CVE-2025-29927) Not affected Affected until patched
React2Shell (CVE-2025-55182) Platform WAF rules plus upgrade Upgrade was the only fix
Cost model Per request / usage Per instance
Control and data residency Platform's Yours

Next.js 16.2 made the Adapters API stable: a supported way for other platforms to hook into the build and consume its output, replacing reverse-engineered build output for providers such as OpenNext-based deployments.


Chapter 6 — instrumentation.ts and OpenTelemetry

instrumentation.ts in the project root (or in src/) is stable since Next.js 15. Its register() runs once when a new server instance starts, before your code. Its onRequestError() (added in 15) receives every error thrown on the server with context: which router, and whether it came from a Server Component, Server Action, Route Handler or Proxy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// instrumentation.ts
import { registerOTel } from '@vercel/otel'

export function register() {
registerOTel({ serviceName: 'shop-web' })
}

export async function onRequestError(
err: unknown,
request: { path: string; method: string; headers: Record<string, string | string[] | undefined> },
context: { routerKind: string; routePath: string; routeType: string },
) {

await fetch(process.env.ERROR_SINK_URL!, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ message: String(err), path: request.path, method: request.method, ...context }),
})
}

Next.js is instrumented out of the box. The spans it emits by default include the root request span (next.span_type BaseServer.handleRequest), App Router rendering (AppRender.getBodyResult), each fetch (AppRender.fetch, disabled with NEXT_OTEL_FETCH_DISABLED=1), Route Handler execution (AppRouteRouteHandlers.runHandler), generateMetadata, and a zero-length start response span at first byte. Attributes include next.route (the route pattern, such as /[param]/user) and next.rsc (whether the request was an RSC request such as a prefetch). NEXT_OTEL_VERBOSE=1 emits more spans.

For a manual NodeSDK setup instead of @vercel/otel, import it only when process.env.NEXT_RUNTIME === 'nodejs', from a separate instrumentation.node.ts.

What to put on dashboards: p95 of start response (time to first byte) per next.route, error counts from onRequestError by routeType, fetch span durations per upstream host, and prefetch (next.rsc) volume after a navigation change.

Watch out: error payloads are a leak vector too. Do not ship full request headers (cookies, authorization) to a third-party sink; pick the fields you need, as above.


Chapter 7 — Environment variables and NEXT_PUBLIC_ leaks

How loading works

Lookup order, stopping at the first hit: process.env, .env.$(NODE_ENV).local, .env.local (skipped when NODE_ENV is test), .env.$(NODE_ENV), .env. Variables can reference others with $VAR. Outside Next.js (an ORM config, a test runner), loadEnvConfig from @next/env loads them the same way.

The inlining rule

Only variables prefixed NEXT_PUBLIC_ reach the browser, and they are inlined as literals at next build. Consequences:

  • One Docker image promoted from staging to production keeps staging's NEXT_PUBLIC_ values. Runtime values must be read on the server during dynamic rendering (await connection() first) or served by an endpoint.
  • Only direct references are replaced: process.env[name] or const env = process.env; env.NEXT_PUBLIC_X are not inlined.
  • The env key in next.config inlines values into the bundle the same way. This blog uses it for NEXT_PUBLIC_BASE_PATH, which is public by nature.

How secrets actually leak

Leak Example Prevention
Wrong prefix NEXT_PUBLIC_STRIPE_SECRET_KEY Code review rule and a CI grep: no SECRET, TOKEN, KEY after NEXT_PUBLIC_ unless explicitly public
next.config env env: { DB_URL: process.env.DB_URL } Never put secrets in env
Props to a Client Component <Map apiKey={process.env.MAPS_SERVER_KEY} /> Props are in the RSC payload: pass results, not keys
Action return values Returning a full user row or config object Return minimal objects (Post 01)
Hardcoded in source db.connect('postgres://user:pw@…') inside a Server Function Environment variables only; CVE-2025-55183 exposed function source
Logs and error sinks Logging process.env or request headers Allow-list fields
Build artefacts .env copied into a Docker image .dockerignore; secrets at runtime, not build time

Structural defences: read process.env only inside the server-only DAL, mark those modules with import 'server-only', and enable experimental.taint so experimental_taintUniqueValue can block a specific secret string from ever being passed to the client.

Common pitfall: "it is only used on the server" is not the same as "it is not in the client bundle". A NEXT_PUBLIC_ reference in any module that a client module imports ships the literal, even if the code path never runs in the browser.


Practice Exercises

Exercise 1 — Survive a Proxy bypass

An app's only protection for /admin is a proxy.ts redirect when the role cookie is not admin. Describe an attack even without a framework CVE, and redesign the checks.

Solution

The cookie is client-controlled: set role=admin and the redirect passes. Even with a signed cookie, any bypass or matcher gap exposes everything. Redesign: Proxy only checks that a session cookie exists; verifySession() in the DAL decrypts and verifies the session and loads the role from the session store; every admin DAL function checks role === 'admin'; every admin Server Action calls those DAL functions.

Exercise 2 — Triage an advisory

A new critical advisory lists "Next.js 15.x and 16.x App Router, fixed in 15.5.x and 16.3.x". You run 15.2.4 self-hosted and a static-export marketing site on 14.2. What do you do for each?

Solution

15.2.4 is inside the affected range and on an old minor: upgrade to the latest 15.5.x patch (the Maintenance LTS line), deploy, and if the advisory is remote code execution and you were exposed, rotate secrets. Then plan the move to 16.x. The static export has no Next.js server, so server-side advisories do not apply; note that 14.x is outside the LTS policy and schedule an upgrade.

Exercise 3 — Fix the image config

A Next.js 16 upgrade makes every product image look slightly different and a designer complains quality={90} is ignored. Explain and fix.

Solution

In 16, images.qualities defaults to [75] and quality is coerced to the nearest allowed value, so 90 becomes 75. Add the needed values: images: { qualities: [75, 90] }. Keep the list short: each allowed quality is another variant attackers can request and the server must cache.

Exercise 4 — Two pods, stale pages

After scaling from one to two pods, users see a product page flip between old and new prices for minutes after an edit. List the causes and fixes.

Solution

Each pod has its own ISR and data cache on local disk and memory, and revalidateTag / updateTag on one pod does not reach the other. Configure a shared cacheHandler (and cacheHandlers for 'use cache: remote'), set cacheMaxMemorySize: 0, and implement refreshTags() so each pod syncs tag invalidations before serving. Also make sure both pods run the same build and share NEXT_SERVER_ACTIONS_ENCRYPTION_KEY.

Exercise 5 — Find the leak

A Server Component renders <Checkout config={config} /> where config = { currency: 'EUR', stripeSecret: process.env.STRIPE_SECRET_KEY, publishableKey: process.env.NEXT_PUBLIC_STRIPE_KEY }. What leaks, how would you detect it, and how do you prevent it structurally?

Solution

stripeSecret is serialised into the RSC payload and visible in page source, even if Checkout never renders it. Detect it by searching the HTML and _rsc responses for the key prefix, or with experimental_taintUniqueValue on the secret, which makes React throw when it is passed to the client. Prevent it by reading the secret only in the DAL, giving Checkout a narrow props type ({ currency, publishableKey }), and marking the config module server-only.

Exercise 6 — Instrument a slow route

Users report slow /products/[id] pages. Using only the default Next.js spans, how do you find whether the time goes to rendering, to upstream fetch calls, or before the first byte?

Solution

Filter traces by next.route = /products/[id]. Compare the root BaseServer.handleRequest duration with the start response span (time to first byte), the AppRender.getBodyResult render span, and the child AppRender.fetch spans grouped by host. Long fetch spans in sequence point to a waterfall (Post 01); a late start response with short fetches points to blocking work outside Suspense (Post 02).


Cheat-sheet

Topic Rule
Real auth boundary The server-only DAL, called by every page, action and handler
Proxy Optimistic checks only
Layout auth Not re-run on child navigation; do not rely on it
CVE-2025-29927 x-middleware-subrequest bypass; fixed 15.2.3 / 14.2.25 / 13.5.9 / 12.3.5
CVE-2025-55182 CVSS 10.0 RSC RCE; React 19.0.1 / 19.1.2 / 19.2.1; Next.js 15.0.5 … 16.0.7
Safe from all follow-ups react-server-dom-* 19.0.4 / 19.1.5 / 19.2.4 or later
LTS Current major Active; previous major Maintenance for 2 years from release
next/image in 16 qualities [75], minimumCacheTTL 4 h, preload replaces priority
next/font Build-time self-hosting, display: 'swap', metric-adjusted fallback
Bundle analyzer next experimental-analyze (16.1+, Turbopack)
Standalone .next/standalone/server.js; copy public and .next/static
Multi-instance Shared cache handler, refreshTags(), encryption key, deploymentId
Observability register() + onRequestError(); @vercel/otel registerOTel
NEXT_PUBLIC_ Inlined at build; frozen per image; only direct references

What's next

This is the last planned post of the core series. The table below lists the extensions. For the underlying layers, the JavaScript Deep Dive series covers the event loop, promises and module semantics that streaming and Server Functions rely on; React Deep Dive covers Suspense, transitions, useActionState and the React Compiler that Next.js 16 supports as a stable option; Vue Deep Dive covers the Nuxt side of the same problems (server rendering, hybrid route rules, Nitro deployment) for teams choosing between the two.


References


Planned future extensions

# Topic Scope
04 Testing Next.js Unit-testing Server Components and actions, Proxy matcher tests, Playwright and the 16.3 instant() navigation helper
05 Internationalisation Locale routing with root params (next/root-params, 16.3), generateStaticParams per locale, caching per locale
06 Multi-tenant SaaS Tenant resolution in Proxy, per-tenant cache keys, isolation in the DAL, custom domains
07 Real-time features Streaming responses from Route Handlers, Server-Sent Events, WebSockets beside Next.js
08 Migrating from the Pages Router Incremental adoption, getServerSideProps to Server Components, data-fetching parity
09 Deep dive: cache handlers Writing a Redis cacheHandler and cacheHandlers, refreshTags(), eviction and failure modes
10 Performance lab Core Web Vitals on a real app: LCP images, INP and hydration cost, partial prefetching trade-offs
11 AI features in Next.js Streaming model output through Server Functions and Route Handlers, timeouts, after() for logging