Tim Neutkens
90d · built 2026-07-24
90-day totals
- Commits
- 57
- Grow
- 4.5
- Maintenance
- 4.5
- Fixes
- 3.1
- Total ETV
- 12
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
→0.0 %
vs 18 prior
↑+47.7 pp
recent vs prior
↑+0.9 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 20 by ETV in the 90-day window.
- 1.4ETVrequest insights: add DevTools request panel (5/5) (#93978) ## What? Adds the experimental Request Insights panel to the Next.js DevTools overlay. The panel provides: - A retained request list with stable selection as new requests arrive. - A “Page load” marker for the exact initial document request. - An end-to-end trace timeline that follows the recorded span hierarchy. - A focused default view of high-level spans and a verbose view for deeper investigation. - Fetch, cache, request, status, duration, and span-count details. - Readable sub-millisecond durations and consistent right-aligned request metrics. This PR is intentionally limited to the Request Insights UI. Additional OpenTelemetry coverage, span display names, and Server Action tracing are split into the dependent PRs below. ## Why? The Request Insights data was already available to tools, but inspecting it required consuming the development endpoint or CLI output. The DevTools panel makes the same request, fetch, cache, and timing information directly inspectable while developing an application. The focused/verbose split keeps the default trace useful for understanding the request lifecycle without hiding the lower-level spans needed for investigation. ## How? - Adds a Request Insights panel and request list to the DevTools overlay. - Builds timeline rows from recorded parent/child relationships instead of assigning synthetic sections. - Uses the request’s full duration as the timeline range and clips outlier spans to that range. - Merges recorded fetch metrics with their corresponding fetch spans. - Preserves the selected request ID until that request is no longer retained. - Compares `request.requestId` with `self.__next_r` to identify only the initial document request. ## Stack The first four Request Insights foundation PRs have already merged. The remaining review stack is: 1. **#93978 — Request Insights UI (this PR)** 2. #95765 — Missing OpenTelemetry spans 3. #95766 — Human-readable OpenTelemetry span names 4. #95767 — Server Action tracing and resolution ## Verification - `pnpm --filter=next build` - `pnpm exec jest packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.test.ts --runInBand` <!-- NEXT_JS_LLM --> --------- Co-authored-by: Tim Neutkens <tim@timneutkens.nl>github.com-vercel-next.js · c427adf4 · 2026-07-14
- 1.3ETVrequest insights: record local framework spans (1/5) (#93974) ## What Adds a dev-only local recorder for framework OTEL spans. This lets Next mirror span data into an in-memory store when local span capture is enabled, without requiring a user to run an OTEL provider. Stack: 1. request insights: record local framework spans (1/5) ← current 2. request insights: derive request history and fetch data (2/5) 3. request insights: expose dev snapshots to tools and HMR (3/5) 4. request insights: add agent diagnosis access (4/5) 5. request insights: add DevTools request panel (5/5) ## Review Focus - Local recorder behavior when no OTEL provider is installed. - Preserving existing OTEL span API behavior when a provider is installed. - Request identity propagation through existing app render async storage. ## Proof In This PR - `pnpm jest packages/next/src/server/lib/trace/tracer.test.ts packages/next/src/server/lib/trace/span-store.test.ts --runInBand` - `pnpm --filter=next types` ## Deferred Coverage - Request-level aggregation, fetch enrichment, dev transport, CLI/MCP access, and the DevTools panel are later PRs in this stack. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tim Neutkens <tim@timneutkens.nl>github.com-vercel-next.js · fab0afcb · 2026-07-12
- 1.1ETVFix Request Insights span collection (#95818) ## Summary Fix Request Insights collection so the dev server records all non-hidden Next.js spans locally, including non-vanilla trace and wrapped spans, while preserving the existing OpenTelemetry export allowlist unless verbose tracing is enabled. Keep reporting timelines correct by propagating request identity before work async storage exists, honoring explicit span timestamps, and anchoring request duration to the BaseServer request span. Standardize span categories on `next.span_category`, retain presentation-only UI filtering, and filter internal request identity headers at ingress. ## Verification - `pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts` <!-- NEXT_JS_LLM -->github.com-vercel-next.js · 491f7809 · 2026-07-16
- 1.0ETVCherry-pick ghsa commits to canary (#93614) ## What? Includes the changes from 16-2. --------- Co-authored-by: JJ Kasper <jj@jjsweb.site> Co-authored-by: Josh Story <story@hey.com> Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com> Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>github.com-vercel-next.js · 647d923a · 2026-05-07
- 0.7ETV(TypeScript 7 Support) Add experimental TypeScript CLI backend (#95639) ## Summary - add `experimental.useTypeScriptCli` so projects can explicitly run their local `tsc` during `next build`, including TypeScript 7 while the legacy JavaScript API is unavailable - preserve the TypeScript API backend as the default, with TypeScript 6-compatible dependency installation and actionable TypeScript 7 migration guidance - load effective compiler metadata through `tsc --showConfig`, preserve inherited path origins, stream native diagnostics, redirect incremental state, and forward worker termination to the compiler process tree - document the experimental behavior and cover TypeScript 6/7, both bundlers, raw diagnostics, full-project checking, dependency selection, and cleanup ## Demo ``` pnpm build > cna@0.1.0 build /Users/timneutkens/projects/sandbox/cna > next build ▲ Next.js 16.3.0-canary.81 (Turbopack) ✓ Running next.config.ts took 23ms - Cache Components enabled - Experiments (use with caution): ✓ useTypeScriptCli Creating an optimized production build ... ✓ Compiled successfully in 3.9s Running TypeScript ... app/page.tsx:8:3 - error TS2322: Type 'string' is not assignable to type 'number'. 8 return count++ + "abc"; ~~~~~~ Found 1 error in app/page.tsx:8 Failed to type check. Next.js build worker exited with code: 1 and signal: null ELIFECYCLE Command failed with exit code 1. ``` ## Verification - `pnpm test-start-turbo test/production/app-dir/typescript-cli/typescript-cli.test.ts` - `pnpm test-start-webpack test/production/app-dir/typescript-cli/typescript-cli.test.ts` - `pnpm test-start-turbo test/production/ci-missing-typescript-deps/index.test.ts` - `pnpm test-dev-turbo test/development/typescript-native-preview/index.test.ts` - `pnpm test-start-turbo test/production/typescript-basic/index.test.ts` <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · a249dcbc · 2026-07-10
- 0.6ETVConvert more tests from createNext -> nextTestSetup (#93799) ## What? Converts more tests that use `createNext` to `nextTestSetup` Follow-up to #93767 --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-next.js · 7da98e13 · 2026-05-18
- 0.6ETVrequest insights: derive request history and fetch data (2/5) (#93975) ## What Builds the bounded request history store on top of local spans and fetch metrics. Request Insights now keeps the last 100 requests, dedupes fetch records, and preserves raw span details for later verbose consumers. Stack: 1. request insights: record local framework spans (1/5) 2. request insights: derive request history and fetch data (2/5) ← current 3. request insights: expose dev snapshots to tools and HMR (3/5) 4. request insights: add agent diagnosis access (4/5) 5. request insights: add DevTools request panel (5/5) ## Review Focus - The request insight schema shared by server, tools, and DevTools. - Fetch cache/status mapping from existing `AppRender.fetch` spans. - Dedupe between completed fetch spans and direct fetch metrics. ## Proof In This PR - `pnpm jest packages/next/src/server/lib/trace/request-insights.test.ts packages/next/src/server/lib/patch-fetch.test.ts --runInBand` - `pnpm --filter=next types` ## Deferred Coverage - Browser/HMR transport, CLI/MCP access, and the visual panel are later PRs. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tim Neutkens <tim@timneutkens.nl>github.com-vercel-next.js · 510d00b5 · 2026-07-12
- 0.5ETVFix instant validation blocking navigations (#95939) ### What? - Run development instant validation only after the navigation response has finished. - Let a newer request supersede stale validation work and yield between validation render attempts. - Forward the cancellation signal into React validation prerenders so asynchronous validation work can stop promptly. - Add focused scheduler/response tests and assert that development validation starts after the response finishes. ### Why? Instant validation was detached from the response promise, but its additional React renders still ran on the same Node.js event loop. A subsequent navigation could therefore wait hundreds of milliseconds for stale diagnostic work from the previous request. Instant validation still runs when the server is idle. When another navigation arrives, foreground request work now takes priority and the obsolete validation is discarded. ### How? Each Cache Components development request receives a validation generation signal. Starting a newer request aborts the previous generation. Validation waits for the Node response `finish` event, yields through the event-loop poll phase between attempts, and combines the generation signal with React's existing prerender abort signals. Tests: - `pnpm --filter=next build` - `pnpm --filter=next types` - `pnpm exec jest --runTestsByPath packages/next/src/server/app-render/dev-validation-scheduler.test.ts packages/next/src/server/app-render/wait-for-response.test.ts` - `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts -t "valid - static prefetch - suspense around runtime and dynamic"` <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 81d0e851 · 2026-07-21
- 0.4ETVrequest insights: expose dev snapshots to tools and HMR (3/5) (#93976) ## What Adds the `experimental.requestInsights` flag, a private dev snapshot endpoint, and HMR snapshot/update transport so running dev servers can expose recent request insights to tools and browser clients. Stack: 1. request insights: record local framework spans (1/5) 2. request insights: derive request history and fetch data (2/5) 3. request insights: expose dev snapshots to tools and HMR (3/5) ← current 4. request insights: add agent diagnosis access (4/5) 5. request insights: add DevTools request panel (5/5) ## Review Focus - Dev-only gating for `experimental.requestInsights`. - Private endpoint behavior when enabled and disabled. - HMR snapshot/update shape and client-side retention. ## Proof In This PR - `pnpm --filter=next types` - Stack-tip Cache Components smoke verifies the endpoint and HMR-fed overlay state against a running dev server. ## Deferred Coverage - A dedicated isolated dev-server transport test can follow if reviewers want cheaper per-PR coverage than the stack-tip smoke. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tim Neutkens <tim@timneutkens.nl>github.com-vercel-next.js · 31541356 · 2026-07-12
- 0.3ETVrequest insights: add agent diagnosis access (4/5) (#93977) ## What Adds `next experimental-diagnose` and an MCP `get_request_insights` tool so agents and local users can inspect Request Insights without opening the DevTools overlay. Stack: 1. request insights: record local framework spans (1/5) 2. request insights: derive request history and fetch data (2/5) 3. request insights: expose dev snapshots to tools and HMR (3/5) 4. request insights: add agent diagnosis access (4/5) ← current 5. request insights: add DevTools request panel (5/5) ## Review Focus - CLI error handling and raw JSON output. - MCP gating when Request Insights is unavailable. - Telemetry registration for the new MCP tool name. ## Proof In This PR - `pnpm jest packages/next/src/telemetry/events/mcp-telemetry.test.ts --runInBand` - `pnpm --filter=next types` - Stack-tip smoke verifies `next experimental-diagnose --url http://localhost:3003 --json` against a real dev server. ## Deferred Coverage - A more curated agent summary format is intentionally deferred until the raw private snapshot contract settles. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Tim Neutkens <tim@timneutkens.nl>github.com-vercel-next.js · 4a4590a3 · 2026-07-13
- 0.3ETVRevert "Revert "refactor: write pages router export files directly to server/pages/"" (#93409) Re-apply #92735 Before re-landing we need to investigate why the pages router middleware i18n test fails when deployed. Update: Investigation/fix: https://github.com/vercel/next.js/pull/92735#issuecomment-4242978559 Reverts vercel/next.js#92733 --- Cursor plan based on the investigation Claude did: # Fix i18n export orphan HTML files in server/pages/ ## Problem When i18n is configured, the export path map in `build/index.ts` creates locale-prefixed entries for every page (e.g., `/en/home/a`, `/fr/home/a`), but only deletes the non-locale entry from `defaultMap` for SSG pages. For non-SSG static pages, the non-locale entry (e.g., `/home/a`) remains, causing the export worker to render and write orphan HTML files directly to `server/pages/`. These orphan files interfere with routing on deployment platforms (e.g., a `[teamId]/[slug].html` orphan matches rewritten paths that should 404). ## Root Cause In [packages/next/src/build/index.ts](packages/next/src/build/index.ts) at line 2988: ```2988:2991:packages/next/src/build/index.ts if (isSsg) { // remove non-locale prefixed variant from defaultMap delete defaultMap[page] } ``` The `if (isSsg)` guard means non-SSG pages keep their non-locale entry, which gets rendered and written to `server/pages/` as an orphan file. ## Fix **Single-line change** in [packages/next/src/build/index.ts](packages/next/src/build/index.ts): Remove the `if (isSsg)` guard so `delete defaultMap[page]` always runs when i18n is enabled. The locale-prefixed entries are already created in the loop above (line 2981), and they capture the original `page` value via `defaultMap[page]?.page || page`. The non-locale entry is not needed after that -- in the old staging-directory flow (`moveExportedPage`), it was rendered but never moved to the final output directory. **Safety verification:** - `updatePagesManifestForExportedPage` (line 3754) is called based on `combinedPages`, not the export map -- it still correctly replaces non-locale manifest entries with locale-prefixed ones - `pageDuration` tracking (line 3745) may return `undefined` for the non-locale path, but this is typed `number | undefined` and used with `|| 0` fallback at line 375 of `build/utils.ts` -- purely cosmetic - SSG pages already have this exact behavior (delete from defaultMap), so this change makes non-SSG consistent with SSG - The `ssgPageDurations` invariant check (line 3737-3738) only runs for SSG pages, which are unaffected ## Test Add assertions to the existing i18n integration test at [test/integration/i18n-support/test/index.test.ts](test/integration/i18n-support/test/index.test.ts) to verify that non-locale-prefixed HTML orphans do not exist in `server/pages/` after build. The test already has production mode assertions checking locale-prefixed file existence and manifest correctness (lines 81-99).github.com-vercel-next.js · c7a5a095 · 2026-05-26
- 0.3ETVKeep the request body a plain Readable after middleware so Readable.toWeb() doesn't hang (#95607) Recreation of https://github.com/vercel/next.js/pull/95370 ### What's the problem? A `POST` (or `PUT`/`PATCH`) request that passes through middleware returning `NextResponse.next()` hangs indefinitely when the downstream handler reads the body via Node's `Readable.toWeb()`. The request never completes and eventually times out. Reproduction: https://github.com/abir-taheer/next-js-readable-stream-bug ### Root cause When middleware runs, `runMiddleware` clones the request body and later calls `finalize()`, which grafts the buffered stream back onto the original `IncomingMessage` via `replaceRequestBody()` (`packages/next/src/server/body-streams.ts`). `replaceRequestBody` copies the buffered stream's enumerable properties onto the request. The buffered stream (`p2`) was a `PassThrough` — a `Duplex` — so its writable-side internals (`_writableState` plus the enumerable `Writable` methods like `write`/`end`) were copied onto the `IncomingMessage`. Because that `_writableState.finished` is `false`, Node stream utilities that inspect it — including `Readable.toWeb()`, which uses `finished()`/end-of-stream detection — treat the request as a still-open writable stream and wait forever. `NextResponse.rewrite()` is unaffected (it builds a new internal request and skips this path), and `GET`/`HEAD` requests are fine because there is no body to clone. ### The fix `p2` is only ever fed with `.push()`, so it never needs a writable side. Making it a plain `Readable` instead of a `PassThrough` keeps the finalized request a pure `Readable`, so `Readable.toWeb()` (and any other duck-typing based on `_writableState`) behaves correctly. No behavior change for the existing consumers, which only read the stream. ### Testing Added `test/unit/body-streams.test.ts`, which drives the real clone → `finalize()` flow and asserts the finalized request: - is no longer writable (`_writableState` is `undefined`), and - is fully consumable via `Readable.toWeb()` (this hangs before the fix). Fixes #95335 <!-- NEXT_JS_LLM_PR --> Co-authored-by: UditDewan <udit.dewan21@gmail.com> Co-authored-by: Baradhan-Madhu <26barum@gmail.com>github.com-vercel-next.js · 3bb780e7 · 2026-07-10
- 0.3ETVfix: preserve middleware rewrite query in Pages API routes (#94905) ### What? - Preserve the resolved routing `query` and dynamic `params` when `NextNodeServer.runApi` invokes a bundled Pages API route. - Keep `req.url` restored to the original browser-visible request URL for compatibility. - Add an end-to-end regression test for a middleware rewrite from `/foo/bar?key=value` to `/api/proxy/bar?added=1&extra=2` using an optional catch-all Pages API route. ### Why? Middleware routing correctly resolves the destination query and catch-all parameters, but the Node Pages API path restores `req.url` before invoking the bundled handler and previously forwarded only `waitUntil`. The handler then reparsed the original URL and produced only `{ key: "value" }`, dropping the rewrite-added `added` and `extra` values and the destination `slug` parameter. This restores the behavior from Next.js 14 that regressed during the Pages API handler-interface migration. The issue affects both webpack and Turbopack because the data is lost in shared server routing after bundling. The public documentation describes the pieces separately—rewrites preserve the original URL, dynamic API parameters appear in `req.query`, and resolved routing queries include middleware changes—so this is a bug fix rather than a new API contract. Fixes #94647 ### How? `runApi` now forwards the existing request metadata together with the resolved `query` and `match.params` through the bundled handler context. The Pages API entrypoint already installs supplied request metadata before `routeModule.prepare()`, which then uses these routed values while retaining the original `req.url`. The regression test asserts that: - `req.url` remains `/foo/bar?key=value`. - `req.query` contains the original `key`, rewrite-added `added` and `extra`, and catch-all `slug` values. ### Verification - `pnpm --filter=next build` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/middleware-rewrites/test/index.test.ts -t "should preserve rewrite query and dynamic params in Pages API routes"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-webpack test/e2e/middleware-rewrites/test/index.test.ts -t "should preserve rewrite query and dynamic params in Pages API routes"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/middleware-rewrites/test/index.test.ts -t "should preserve rewrite query and dynamic params in Pages API routes"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack test/e2e/middleware-rewrites/test/index.test.ts -t "should preserve rewrite query and dynamic params in Pages API routes"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/api-support/api-support.test.ts -t "query|dynamic"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack test/e2e/api-support/api-support.test.ts -t "query|dynamic"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/e2e/custom-routes/custom-routes.test.ts -t "api rewrite"` - `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack test/e2e/custom-routes/custom-routes.test.ts -t "api rewrite"` - Pre-commit lint-staged checks (Prettier and ESLint) - Not run continuously: `pnpm --filter=next dev` (the local watcher hits the environment open-file limit with `EMFILE`; the focused package build passed) <!-- NEXT_JS_LLM_PR --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes shared Pages API request routing in the Node server and adapter i18n route generation; behavior is restored to pre-regression semantics but affects all bundled Pages API invocations. > > **Overview** > Fixes a regression where **Pages API** handlers saw only the original URL query after a middleware rewrite, because `runApi` restored `req.url` but did not pass the router’s resolved `query` and `params` into the bundled handler context. > > `NextNodeServer.runApi` now supplies `requestMeta` (including merged `query` and `match.params`) alongside `waitUntil`, so `req.query` keeps rewrite-added parameters and catch-all segments while `req.url` stays the browser-visible path. > > Adapter build output no longer applies i18n locale prefixing to dynamic **Pages API** routes (`shouldLocalize` excludes API pages); regular pages still get `nextLocale` routing. > > Regression coverage: middleware rewrite e2e (`/foo/bar` → catch-all API) and a production adapter test with `i18n` enabled. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ad415cfe574f67dd7385662a01c2c9f3c89af521. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->github.com-vercel-next.js · 668cd3a4 · 2026-06-26
- 0.2ETVTrace cacheHandler and cacheHandlers when using adapters (#94197) ### What? Add focused production adapter coverage for configured `cacheHandler` and `cacheHandlers` module graphs in Node `output.assets`, including transitive helper dependencies and Turbopack `assetsHashes` assertions. ### Why? Adapter deployments dynamically load configured cache handlers at runtime. This coverage demonstrates that Node adapter outputs currently omit those handler files and their transitive dependencies. ### How? Add an App Router Cache Components fixture using `adapterPath`, both cache-handler APIs, and a `use cache: remote` route. Its adapter writes `onBuildComplete` context to disk and the test inspects Node outputs for each handler and uniquely named helper. ### Verification - `NEXT_TEST_PREFER_OFFLINE=1 pnpm --filter=next build` - Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts` (no Node output contains all four handler assets) - Expected failure: `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack test/production/app-dir/adapter-cache-handlers/adapter-cache-handlers.test.ts` (same adapter handoff gap) - `git diff canary --check` - Not run continuously: `pnpm --filter=next dev` (`EMFILE: too many open files` occurred after startup in the local watcher) <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>github.com-vercel-next.js · d342d0b9 · 2026-06-03
- 0.2ETVDeprecate edge runtime (#93369) Note: re-apply of #92817 ### What? Deprecates the Edge Runtime and the `preferredRegion` route segment config that only applied to edge runtime with build-time warnings, TypeScript IDE hints, error documentation pages, and documentation updates. ### Why? Edge Runtime in Next.js is being deprecated in favor of the Node.js runtime (the default). The `preferredRegion` route segment config is tightly coupled to Edge Runtime and is also being deprecated. ### How? **Edge Runtime deprecation:** - Added `warnAboutEdgeRuntime()` helper using `Log.warnOnce` in `packages/next/src/build/warn-about-edge-runtime.ts` - Warning fires in `get-page-static-info.ts` (Webpack dev/build + Turbopack build) and `turbopack-utils.ts` (Turbopack dev) when `runtime = 'edge'` is detected - Marked `'edge'` as `@deprecated` in the TypeScript language service plugin (`rules/config.ts`) - Created `errors/edge-runtime-deprecated.mdx` with migration guidance - Updated documentation across 11 files to add deprecation notices or simplify/remove outdated Edge Runtime references **`preferredRegion` deprecation:** - Added `warnAboutPreferredRegion()` helper in the same file - Warning fires in `get-page-static-info.ts` for both App Router (`config.preferredRegion`) and Pages Router (`config.config?.regions`) - Marked `preferredRegion` as `@deprecated` in the TypeScript language service plugin and the generated types (`next-types-plugin`) - Created `errors/preferred-region-deprecated.mdx` with migration guidance (remove the export) - Updated documentation across 4 files to add deprecation notices **Tests:** - Added `test/e2e/edge-runtime-deprecated/` with fixture and assertions for the edge runtime warning (dev + production) - Added `test/production/preferred-region-deprecated/` with fixture and assertions for the preferredRegion warning (production only -- Turbopack dev does not surface `preferredRegion` from the Rust layer to JS) <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>github.com-vercel-next.js · 84f1e44d · 2026-04-30
- 0.2ETVDeprecate undocumented custom server methods (#94348) ### What? Deprecate undocumented custom-server methods exposed by `next()` while preserving their runtime behavior. - Add TypeScript deprecation annotations and one-time runtime warnings for `setAssetPrefix()`, logging helpers, `revalidate()`, and legacy `render*` helpers. - Extend custom-server tests to cover warnings and warning deduplication. - Move unrelated test fixtures away from deprecated custom-server calls. ### Why? These methods are not part of the documented custom-server API, but existing applications may still rely on them. Warning-only deprecation gives users migration guidance without introducing a breaking runtime change. ### How? Warnings are emitted only through `NextCustomServer`, so framework-owned `NextServer` usage remains quiet. The warning text points `render*` callers toward `app.getRequestHandler()`, `setAssetPrefix()` callers toward configuration, and logging and revalidation callers toward supported application APIs. The tests retain compatibility assertions for existing behavior, add deliberate invocations for previously uncovered methods, verify once-per-process warning deduplication, and remove incidental warning sources in unrelated fixtures. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 20ac9ce9 · 2026-06-03
- 0.2ETVPrerender static metadata under dynamic segments to canonical pathname (#93873) ### What? Fixes an invariant failure (`failed to find source route /dynamic/[id]/apple-icon.png for prerender /dynamic/[id]/apple-icon.png`) raised by the build adapter when an app contains static metadata files (`apple-icon.png`, `icon.png`, `opengraph-image.png`, `twitter-image.png`, `sitemap.xml`) under a dynamic segment. ### Why? Static metadata files under a dynamic segment were treated as full dynamic prerenders: they ended up in `prerenderManifest.dynamicRoutes` with `fallbackRouteParams`, but no `.body` file was exported and they never received an `appOutputMap` entry. The adapter's `dynamicRoutes` loop then asked `getParentOutput` for a source route that didn't exist and threw. These files are content-stable across params — the route handler returns the same bytes for every URL that matches the dynamic pattern — so they should prerender once under a canonical pathname rather than be treated like per-param dynamic routes. ### How? - New `getStaticMetadataPrerenderPathname` helper that maps `/dynamic/[id]/apple-icon.png` to `/dynamic/-/apple-icon.png`. This is the canonical pathname already used by `fillStaticMetadataSegment` for HTML link emission. - In `build/utils.ts`, route `APP_ROUTE` entries that are static metadata files through a new `buildStaticMetadataStaticPaths` (one prerender path, no fallback params) instead of the regular `buildAppStaticPaths`. - In `build/index.ts`: - Rewrite each prerendered static metadata route to its `-`-placeholder pathname before splitting into known/unknown buckets, so it lands in `prerenderManifest.routes` with a `.body` file. - Skip static metadata entries in the `dynamicPrerenderedRoutes` loop so they don't get a stray `PRERENDER` manifest entry. - Defensive sweep before write: drop any leftover bracketed metadata keys from `prerenderManifest.dynamicRoutes`. - In the adapter, look up `prerenderManifest.routes` under the canonical placeholder pathname (with locale prefix when applicable) and skip static metadata in the `dynamicRoutes` loop. - Unit tests in `get-metadata-route.test.ts` lock in the placeholder behavior for the tricky cases: catchall, optional catchall, multi-level dynamic, literals between dynamics, dynamic + catchall mix. Reproduction: `test/e2e/app-dir/metadata-static-file/metadata-static-file-dynamic-route.test.ts` configured with `adapterPath` previously crashed at `handleBuildComplete`; all three cases in that file now pass. Runtime stays the same: arbitrary param URLs continue to be served by the dynamic route handler shipped with the app route; only the canonical `-` URL is statically served. <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 765b39c4 · 2026-05-22
- 0.2ETVAdd attribute rendering benchmark (#95621) ### What? Add two dynamic routes to the render-pipeline benchmark and include both in the default route suite: - `/attributes` renders 1,000 rows with string, data, ARIA, title, and inline-style attributes. - `/tailwind` renders a realistic dashboard with dense Tailwind-style utility class names across navigation, metrics, project cards, and an activity table. ### Why? The existing benchmark routes focus on lightweight rendering or Suspense and Flight payload stress. They do not provide focused integration coverage for React Fizz attribute serialization. `/attributes` provides a synthetic stress case modeled after the upstream React benchmark for facebook/react#36899. `/tailwind` complements it with a production-like component tree and realistic class strings, including responsive, state, dark-mode, and arbitrary-value utilities. ### How? Both fixtures run through the real Next.js production-server render-pipeline harness and are forced dynamic so every request exercises server rendering. The Tailwind-style fixture contains 18 project cards, 36 activity rows, and 101 `className` attributes. It intentionally does not install or configure Tailwind because the benchmark targets React serialization of the class strings, not generated CSS or visual styling. The new routes are registered in the default stress suite and documented in the benchmark playbook and render-pipeline README. ### Verification - Prettier for all changed files - ESLint for both benchmark fixtures and `bench/render-pipeline/benchmark.ts` - Production `next build` + `next start` smoke benchmark for `/tailwind`; single-client and under-load phases completed with zero request errors - Three interleaved base/head `/attributes` runs with 500 serial and 5,000 loaded requests - `/attributes` PR-head median throughput: +8.26% single-client and +10.85% under load - `/attributes` PR-head median p95: 7.91% better single-client and 9.61% better under load - `git diff --check` <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 836ba370 · 2026-07-09
- 0.2ETVAdd deployable tarballs to pack-next (#93963) ### What? Adds a `--deployable-tar` option to `pack-next` that writes generated tarballs into the target project and patches package references to use relative `file:` paths. ### Why? Projects that need to deploy with locally packed Next.js packages should be able to include those tarballs inside the project directory instead of referencing tarballs from the Next.js checkout. ### How? Resolves the target project package.json location, writes tarballs to a sibling `tarballs/` directory, and maps package override paths to project-relative references when patching package.json. Existing `--tar` behavior continues to write to the repository tarballs directory. ### Verification - `git diff --check` - `git diff --cached --check` - `pnpm prettier --with-node-modules --ignore-path .prettierignore --write scripts/pack-next.ts scripts/pack-utils/patch-package-json.ts contributing/core/testing.md contributing/core/developing.md` - `npx eslint --config eslint.config.mjs --fix scripts/pack-next.ts scripts/pack-utils/patch-package-json.ts contributing/core/testing.md contributing/core/developing.md` (Markdown files were ignored because no matching ESLint config was supplied) - `pnpm pack-next --project ../sandbox/nextjs-duplicated-html-repro/ --deployable-tar` <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · cdd77ac4 · 2026-05-20
- 0.2ETVFix dev overlay hydration error ordering (#94555) ### What? Preserve FIFO ordering for events received while the dev overlay connects its React dispatcher. - Replace the separate global dispatch pointer and backlog with an `EventQueue`. - Keep new events queued until the existing backlog has finished replaying. - Add focused unit coverage for normal ordering, events enqueued during replay, and replay failures. ### Why? The React 18 Pages Router hydration test for an extra node inside Suspense reports three events in this order: 1. The runtime hydration mismatch captured from the error/console path. 2. The recoverable hydration mismatch. 3. The recoverable Suspense boundary client-render fallback. The overlay previously set `maybeDispatch` from `useInsertionEffect`, but deferred replaying its existing queue with `setTimeout`. If the first event arrived before the overlay connected, then the remaining recoverable events arrived after `maybeDispatch` was exposed but before the timeout, those newer events dispatched immediately and overtook the queued event. The resulting order was 2, 3, 1, which caused the inline snapshot failure. Investigation also found that enabling Node.js streams was correlated with the failure but was not its direct cause: - The failing test uses the Pages Router renderer, which still calls `ReactDOMServerPages.renderToReadableStream` and does not select the App Router Node Fizz stream implementation. - The Node streams default change did not modify the Pages client error bridge or dev overlay queue. - The queue race predates the Node streams rollout. Timing changes around the rollout, including adjacent lazy loading of the overlay UX, made the existing race reproducible. The correct fix is therefore to enforce the overlay queue ordering invariant rather than update the snapshot or special-case Node streams. ### How? `EventQueue.connect()` leaves direct dispatch disabled while draining the backlog. Events received during replay are appended to the same queue and drained before the dispatcher becomes directly available. A `finally` block preserves the previous cleanup and connection behavior when a queued event throws, while `disconnect()` only clears the currently connected dispatcher. ### Verification - `pnpm prettier --with-node-modules --ignore-path .prettierignore --write packages/next/src/next-devtools/dev-overlay.browser.tsx packages/next/src/next-devtools/dev-overlay/event-queue.ts packages/next/src/next-devtools/dev-overlay/event-queue.test.ts` - `npx eslint --config eslint.config.mjs --fix packages/next/src/next-devtools/dev-overlay.browser.tsx packages/next/src/next-devtools/dev-overlay/event-queue.ts packages/next/src/next-devtools/dev-overlay/event-queue.test.ts` - `pnpm --filter=next build` - `pnpm exec jest packages/next/src/next-devtools/dev-overlay/event-queue.test.ts --runInBand` - `HEADLESS=true NEXT_TEST_REACT_VERSION=18.3.1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-webpack test/development/acceptance/hydration-error.test.ts -t "should show correct hydration error when client renders an extra node inside Suspense content"` - `HEADLESS=true NEXT_TEST_REACT_VERSION=18.3.1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/development/acceptance/hydration-error.test.ts -t "should show correct hydration error when client renders an extra node inside Suspense content"` <!-- NEXT_JS_LLM_PR -->github.com-vercel-next.js · 45d26dfd · 2026-06-18