Commit Graph

1373 Commits

Author SHA1 Message Date
github-actions[bot] 05eee9fecc chore: version packages (beta) (#2673)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-24 16:23:30 +01:00
James Anderson 1c69ce3fef fix(pages-router): load custom _app/_document via resolved file paths in dev (#2692)
* fix(pages-router): load custom _app/_document via resolved file paths in dev

The dev server imported pages/_app and pages/_document by extensionless id
and relied on the module runner applying custom resolve.extensions (e.g.
".page.tsx" from pageExtensions). The runner no longer resolves those, so
apps using pageExtensions silently lost their custom App/Document (the load
failure is swallowed). Import the file path resolved by findFileWithExts
instead, at all three call sites.

* test(e2e): add pages-router-complex example app and behaviour suite

A deliberately convoluted Pages Router app that serves as a compatibility
target: the kinds of patterns that only surface in large, long-lived
enterprise pages-router codebases, each pinned by a Playwright spec whose
behaviour is verified against real Next.js (73/73 under next dev --webpack).

Highlights: custom pageExtensions everywhere (middleware.page.ts,
instrumentation.page.ts), app-shell getInitialProps with a TTL-memoised
chrome fetch and embedded-shell/draft bypasses, class _document with its own
GIP, a zone route dimension driven by middleware rewrites and a real
i18next/react-i18next runtime, catch-all routes with static-sibling
precedence and cacheable-404/redirect hygiene, record-driven template
branching on a dynamic/static/dynamic route, an urql-style server-snapshot
data layer, shallow routing + router.events + next/compat/router +
next/navigation hooks in pages, next/image with a custom loader, draft-mode
gateway APIs, a function-form next.config, and a Cloudflare setup matching
the vinext init scaffold.

The playwright project (pages-router-complex, port 4199) is intentionally
not in the CI e2e matrix: vinext currently passes 59/73, and the failures
are the compat backlog documented in the example README.

* fix(pages-router): cover compound page extensions in CI
2026-07-24 15:35:16 +01:00
James Anderson 956bed233b fix(pages): preserve live Set-Cookie header arrays (#2689)
* fix(pages): preserve live Set-Cookie header arrays

* fix(pages): align writeHead cookie replacement
2026-07-24 14:29:27 +01:00
MaxtuneLee 756d7eeee4 fix(css): load Sass partials from paths containing tildes (#2691)
* fix(css): normalize Sass importer stylesheet keys

* perf(sass): simplify path handle

* style(sass): simplify stylesheet key handling
2026-07-24 11:22:51 +01:00
Ching Wei Kang 431b044879 fix: honor build --mode dotenv files (#2523)
* fix: honor build mode dotenv files

* fix: preserve test-mode dotenv exclusion in config

* fix(build): keep NODE_ENV production with custom mode

* fix(build): set production env before config loading

* fix(env): preserve dotenv expansion operators

* fix(build): preserve explicit NODE_ENV for config loading

---------

Co-authored-by: James <james@eli.cx>
2026-07-23 20:30:53 +01:00
MaxtuneLee f2252edf15 fix(build): normalize jsx-in-js module ids on Windows (#2687) 2026-07-23 15:32:28 +01:00
MaxtuneLee 052b2661ab fix(rsc): handle Windows paths in client reference dedup (#2686) 2026-07-23 14:18:18 +01:00
James Anderson 5f259cee96 fix(app-router): install default not-found boundary (#2670)
* fix(app-router): install default not-found boundary

* fix(app-router): own default not-found at root layout
2026-07-23 13:51:48 +01:00
James Anderson 8e7fa22491 fix(app-router): scope dynamic params by segment (#2228)
* fix(app-router): scope dynamic params by segment

* Reviewed PR #2228: correct, approved

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* fix(app-router): render static param misses through not-found

* fix(app-router): preserve context for static param 404s

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-07-23 13:50:27 +01:00
James Anderson 8e36c914e0 fix(app-router): honor client cache stale times (#2449)
* fix(app-router): honor client cache stale times

* fix(app-router): preserve static reusable prefetch stale time

* fix(app-router): reset fresh page state after prefetch

* fix(app-router): preserve intercepted source state

* fix(app-router): preserve client cache segment identity

* fix(app-router): preserve dynamic slot cache semantics
2026-07-23 13:48:42 +01:00
James Anderson 22f106e18d perf(app-router): serialize streamed metadata once (#2675) 2026-07-22 21:00:40 +01:00
James Anderson e7800ad75f fix(app-router): resolve interception-only RSC targets (#2256)
* fix(app-router): dispatch interception-only targets

* fix(app-router): preserve interception action rerenders

* fix(app-router): align interception request path matching

* test(app-router): distinguish encoded interception paths

* test(app-router): preserve intercepted source params
2026-07-22 20:36:49 +01:00
James Anderson 95661c2192 fix(cache): preserve prerendered page cache tags (#709)
* feat(cache): implement Next.js 16 revalidateTag two-phase stale/expired model

- Add TagRevalidationDurations interface and update CacheHandler interface
- Rewrite MemoryCacheHandler to use stale/expired TagManifestEntry model
- Rewrite KVCacheHandler with KVTagEntry JSON format (backward-compat with legacy plain-timestamp)
- Add deprecation warning to public revalidateTag() when called without profile
- SWR semantics when profile with expire>0: mark stale immediately, hard-expire after window
- Hard invalidation when no profile or expire=0: set expired=now, next get() is a miss
- Fix >= comparisons for same-millisecond set()+revalidateTag() correctness
- Add tests for deprecation warning, SWR stale return, expire=0 hard miss, JSON KV format

* refactor(cache): address bonk review comments

- Document >= vs > as deliberate divergence from Next.js with rationale
  (same-millisecond set+revalidateTag must invalidate; strict > would allow
  stale serves when both events share a timestamp)
- Export TagRevalidationDurations interface from next-shims.d.ts and use it
  in CacheHandler, MemoryCacheHandler, and revalidateTag signatures
- Remove dead ...existing spread in MemoryCacheHandler.revalidateTag — both
  branches fully overwrite the TagManifestEntry, the spread could accidentally
  preserve stale fields if the interface grows
- Tighten deprecation warning test regex to match the exact emitted message

* fix(cache): match Next.js updateTags branch logic for durations without expire

When revalidateTag is called with a truthy durations object, stale is now
always written to the tag manifest regardless of whether expire is set.
The expired field is only set when durations.expire !== undefined.

This fixes two divergences from Next.js default.ts updateTags:
- revalidateTag('tag', {}) → { stale: now } (stale-only SWR), was { expired: now }
- revalidateTag('tag', { expire: 0 }) → { stale: now, expired: now }, was { expired: now }

Applies the same fix to both MemoryCacheHandler and KVCacheHandler.
Adds tests covering the new stale-only ({}) and expire=0 shapes.

* fix(cache): reuse now variable in MemoryCacheHandler.get for time-based expiry check

* fix(cache): preserve prerendered page cache tags
2026-07-22 20:33:12 +01:00
James Anderson c1b47304c5 fix(app-router): preserve hash query navigation semantics (#2669) 2026-07-22 20:31:44 +01:00
James Anderson a09207618a fix(pages): normalize decoded edge responses (#2668)
* fix(pages): normalize decoded edge responses

* fix(pages): thread hybrid edge runtime
2026-07-22 17:56:12 +01:00
James Anderson eb3b15fdc2 fix(app-router): reconcile streamed metadata icons (#2320)
* fix(app-router): reinsert streamed metadata icons

* fix(app-router): harden streamed metadata icons

* fix(app-router): make streamed icon insertion hydration-safe

* fix(app-router): reconcile all streamed metadata icons

* fix(app-router): preserve distinct streamed icon descriptors

* fix(app-router): match metadata icon descriptor parity

* fix(app-router): sanitize streamed metadata icons

* test(app-router): allow streamed metadata siblings

* test(app-router): ignore icon attribute order

* fix(app-router): preserve parser-time icon order
2026-07-22 17:51:51 +01:00
James Anderson 5fc6fb322b fix(pages): propagate Document script security props (#2044)
* fix(pages): propagate document asset props

* fix(pages): scope document asset props in dev

* fix(pages): preserve default asset tag attributes

* fix(pages): apply document asset props in default dev shell

* fix(pages): apply document props after dev transforms

* fix(pages): scope document asset marker cleanup

* fix(pages): preserve document asset ownership

* fix(pages): preserve document asset prop ownership

* fix(pages): avoid backtracking when adding asset props
2026-07-22 17:46:42 +01:00
James Anderson 5b6d0c867d fix(app-router): align prefetch server protocol (#2318)
* fix(app-router): align prefetch server protocol

* fix(app-router): keep prefetch state type internal

* fix(app-router): avoid consuming partial prefetches

* test(app-router): explicitly ignore router push result

* fix(app-router): preserve full prefetch semantics

* fix(app-router): preserve explicit full prefetch requests

* fix(app-router): guard malformed next-url inference

* test(app-router): clarify slot loading boundaries

* fix(app-router): publish transitional cache snapshots

* fix(app-router): canonicalize prefetched response URLs

* fix(app-router): preserve cross-origin prefetch responses
2026-07-22 13:12:09 +01:00
github-actions[bot] 3d30a3ca36 chore: version packages (beta) (#2632)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-21 23:24:35 +01:00
Iago Lima Toledo 023aa76403 fix(metadata): add meta's 2024 crawler UAs to the html-limited bot list (#2666)
* fix(metadata): add meta's 2024 crawler UAs to the html-limited bot list

Meta introduced meta-externalagent and meta-externalfetcher in 2024. The
default html-limited bot list (mirrored from Next.js) only carries the
legacy facebookexternalhit, so the new UAs get streamed metadata. Meta's
crawlers don't execute JS, so every og:* tag lands after </head> and
link previews on WhatsApp/Instagram/Facebook render blank.

Deliberate, documented divergence from Next.js's default list (which
predates Meta's UA migration) per AGENTS.md. A user-provided
htmlLimitedBots config still replaces the whole list.

Test: new UAs get blocking metadata, the legacy UA stays covered, and a
custom htmlLimitedBots config still replaces the default.

* refactor(metadata): share html-limited bot detection

---------

Co-authored-by: MrIago <28714061+MrIago@users.noreply.github.com>
Co-authored-by: James <james@eli.cx>
2026-07-21 23:17:55 +01:00
James Anderson 6f65eaab38 fix(app): log RSC render errors on the dev-server terminal (#2667)
* fix(app): log RSC render errors on the dev-server terminal

`reportRequestError` is a no-op when no `onRequestError` instrumentation
hook is registered, so a server-component render error was swallowed
silently in the dev server. Next.js's instrumentation wrapper logs render
errors in development regardless of user instrumentation; mirror that by
emitting a `console.error` from the RSC onError handler in dev.

The `!hasDigest` guard dedupes: the same error object reaches the handler
on both the RSC and SSR/HTML render passes, and the first pass stamps it
with a digest — matching Next.js's `silenceLog` dedup so it logs once.

* fix(app): ignore expected RSC render cancellations

* docs(app): clarify digest log suppression
2026-07-21 22:51:36 +01:00
Nathan Nguyen 88ed49e11d perf(build): cache repeated compatibility transforms (#2578)
* perf(build): cache repeated compatibility transforms

App Router builds run pure dynamic-request and typeof window transforms repeatedly across RSC, SSR, and analysis passes. Re-parsing identical module input adds build work without changing output.

The transform hooks now reuse results for exact module id, source, and environment replacement keys while replacing stale per-id source entries. Focused tests cover cache reuse and key separation.

* perf(build): reuse pure compatibility transforms

Repeated build environments can parse and rewrite the same module source more than once. The transform result is deterministic once the module id, source, and environment-derived variant are fixed.

Share the existing bounded per-module cache across the compatible transform plugins and cover source, module, variant, and null-result boundaries.

* fix(build): invalidate cached source identities after junction retargets

Repeated transforms can keep emitting import.meta.url and CJS globals for a junction's previous canonical target when the raw ID and source stay unchanged.

The import-meta-url cache variant omitted the canonical path even though the rewrite derives its output from that path. Include canonicalId in the variant and cover retargeting between identical source files.

* ci: rerun performance benchmarks

* perf(build): avoid composite import-meta cache keys

Eligible import-meta transforms allocate and hash a composite string containing the canonical root and module path on every invocation, including cache hits. That adds deterministic work to dev cold start.

Keep source, canonical root, and canonical id as direct equality fields in a per-module entry. Retain only the two-value environment result map so canonical-path invalidation remains correct without composite key allocation.

---------

Co-authored-by: James <james@eli.cx>
2026-07-21 22:04:22 +01:00
Nathan Nguyen 2824b7653a fix(router): replace stale optimistic layouts across dynamic params (#2609)
* fix(router): replace stale optimistic layouts across dynamic params

A detached optimistic shell can commit stale dynamic-layout output before the authoritative payload resolves. Preparing the latter from live router state then makes the shell appear current, so stale server props and BFCache identity survive a cross-param navigation.

All payloads in one navigation must derive reuse identity from the same initiation state. Capture that state once and pass it through commit preparation, while live router state remains the authority for cancellation and commit approval.

Add composition coverage for cross-param replacement, same-param preservation, and layout-owned slots, plus a deterministic browser regression for the prefetched-shell handoff.

* fix(router): require navigation initiation state for payload preparation

Navigation payload callers could omit the initiation state and silently prepare from live router state. A future caller could therefore compile while reintroducing the optimistic-to-authoritative identity bug.

Require the state at both browser-entry and controller boundaries and remove the live-state fallback. Isolated controller tests now choose current-state preparation through an explicitly named test helper.

* test(router): synchronize payload tests on state dispatch

Navigation payload regressions advanced a fixed number of microtasks before reading router state. That coupled the tests to the controller's current async scheduling depth.\n\nExpose a one-shot visible-commit dispatch waiter from the controller harness and await that explicit boundary before assertions.

* docs(router): document the currentState baseline in createPendingNavigationCommit

createPendingNavigationCommit's currentState param has no note on what
it should be. Navigation callers now pass the frozen navigation-initiation
state (per the previous two commits), while the HMR caller still passes
live state, and nothing marks that split as deliberate.

A future navigation call site that passes live state instead of the
initiation state would silently reintroduce the stale cross-param reuse
bug this branch fixes, with no type error to catch it.

Document the invariant on the field so the split reads as intentional.

---------

Co-authored-by: James <james@eli.cx>
2026-07-21 21:31:12 +01:00
Nathan Nguyen 060de14a0b fix(cloudflare): report custom-domain deploy URLs (#2630)
* fix(cloudflare): report custom-domain deploy URLs

Cloudflare deploys that disable workers.dev currently finish with "(URL not detected in wrangler output)" even when Wrangler confirms a custom-domain target. Wrangler emits custom domains as bare hostnames, while vinext only parses HTTPS workers.dev URLs.

Parse Wrangler custom-domain target markers, validate the hostname as an HTTPS origin, and ignore wildcard routes and disabled domains. Preserve workers.dev URL precedence and cover the deploy boundary plus Wrangler 4.110 output variants.

* ci: retry flaky unit test shard

The upstream unit-test shard failed while removing a temporary typegen directory after all test assertions passed. The affected test is unrelated to this branch and passed 20 consecutive targeted runs locally.\n\nTrigger a fresh GitHub Actions run without changing the pull request diff.

---------

Co-authored-by: James <james@eli.cx>
2026-07-21 21:18:24 +01:00
Andrew ba34531727 perf(build): split react-dom/server into its own client chunk (#2604)
* perf(build): split react-dom/server into its own client chunk

createClientManualChunks keyed the always-loaded "framework" chunk on the bare
package name ("react-dom"), so react-dom/server.browser + its cjs implementation
rode along on every page even though only client code that renders to a string
(e.g. an embedded Sanity Studio) imports it. Route the server/static renderer
entrypoints to a dedicated "react-dom-server" chunk so framework stays ~35KB
brotli lighter on every page; the server renderer loads only where used.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(build): normalize react-dom server chunk ids for Windows

* test(build): cover react-dom server chunk entrypoints

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-07-21 14:12:38 +01:00
ponharu 24eb360d22 fix(metadata): pass parent to cached resolvers with default or rest p… (#2660)
* fix(metadata): pass parent to cached resolvers with default or rest parameters

* fix(metadata): preserve parent for opaque cache exports

---------

Co-authored-by: James <james@eli.cx>
2026-07-21 11:29:54 +01:00
James Anderson d4d02cb805 fix(link): interpolate Pages Router dynamic hrefs (#2657)
* fix(link): interpolate Pages Router dynamic hrefs

* PR #2657 review: LGTM

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-07-21 00:53:51 +01:00
Christoph Richter 3bef10c56b fix(init): complete an existing Cloudflare config instead of only adding to it (#2653)
* fix(init): complete an existing Cloudflare config instead of only adding to it

`vinext init --platform=cloudflare` generates a correct wrangler.jsonc when
there isn't one, but when a wrangler.jsonc already exists it only merged
`cache`, `images` and `kv_namespaces` into it. `main` and `assets` were never
added and never warned about, so the two paths disagreed about what a valid
Cloudflare config is and only one of them was checked.

Without `main`, @cloudflare/vite-plugin builds the project as assets-only: the
RSC environment emits no dist/server/wrangler.json, the `start:vinext` and
`deploy:vinext` scripts init just wrote point at a file that no longer exists,
and the deploy falls back to the assets-only config. It reports success and
ships a Worker with no SSR entry, so static assets return 200 and every route
returns 404 with no index.html to fall back on. Nothing in the output points
at `main`.

The vite config had the same shape of bug: `ensurePlugins` only adds plugins
that are absent, so an existing bare `cloudflare()` kept its defaults and an
App Router project silently lost
`viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] }`.

- `updateWranglerConfigForCloudflare` now fills in `main` and `assets` when
  absent, reusing the worker-entry resolution `generateWranglerConfig` already
  used, so both paths produce the same config. Existing values are left alone
  and the update stays idempotent.
- `ensureCloudflareViteEnvironment` adds `viteEnvironment` to an existing
  `cloudflare()` call for App Router projects, whether it is called bare or
  with other options.

Reproduced on 1.0.0-beta.2 with a generator that emits an app and its
wrangler.jsonc together, then runs `vinext init --platform=cloudflare`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(init): validate existing Cloudflare config

---------

Co-authored-by: piffie <1213363+piffie@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-07-20 23:25:43 +01:00
Boy Steven bc89793f21 fix(i18n): align Accept-Language locale selection (#2648)
* fix(i18n): align Accept-Language locale selection

* test(i18n): align regional locale expectations

* chore(ci): rerun flaky unit shard

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 23:20:08 +01:00
Boy Steven 6fbb280495 fix(shims): preserve basePath config in NextURL clones (#2647)
Co-authored-by: James <james@eli.cx>
2026-07-20 23:17:55 +01:00
ponharu 3e444ecec4 fix(metadata): pass parent to regular metadata resolvers (#2646)
Co-authored-by: James <james@eli.cx>
2026-07-20 23:16:32 +01:00
Andrew 8ab7a663f6 fix(fonts): keep immutable font assets query-free (#2605)
* fix(fonts): don't append ?dpl= to font preload hrefs

The @font-face src URLs (inline style block and built CSS) are emitted
bare, and a preload only matches a font request when the URLs are
byte-identical. Appending the deployment-id query to the preload hrefs
made every font preload a wasted download and re-fetched each font a
second time once the CSS parsed — measurably late, inside the LCP
window. Font files are content-hashed, so the query added no
cache-busting value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(fonts): drop ?dpl= from Pages Router font preloads

The Pages Router had the same bug fixed for the App Router in the
previous commit, in two places: buildPagesFontHeadHtml wrapped the HTML
<link rel="preload"> hrefs in appendAssetDeploymentIdQuery, and the
pages handler's HTTP Link: header did the same — while the @font-face
src URLs in the <style data-vinext-fonts> block are emitted bare. A
preload only matches a font request when the URLs are byte-identical,
so every Pages font preload was a wasted download. Fonts are
content-hashed, so ?dpl= added no cache-busting value.

Adds a Pages Router production test (new pages fixture with
next/font/google) pinning a deploymentId and asserting both the HTML
preload hrefs and the Link: header URLs are query-free and
byte-identical to the @font-face src URLs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(fonts): preserve deployment IDs across font URLs

* fix(fonts): keep immutable font assets query-free

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-07-20 23:01:29 +01:00
ponharu 5494d182d2 feat(metadata): support viewport fields and parent resolution (#2644)
* feat(metadata): support viewport fields and parent resolution

* fix(metadata): resolve primary viewport before parallel slots

* fix(metadata): match viewport resolution ordering

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 23:01:16 +01:00
Andrew a5aedda4de fix(font-google): share SSR collection state via globalThis (#2607)
* fix(font-google): share SSR collection state via globalThis

In Vite's multi-environment dev mode the font-google-base shim can be
loaded more than once in the same worker (e.g., resolved through
different IDs across the rsc/ssr environments), so each module copy
ends up with its own freshly-initialized closure variables. The
fontLoader call site and the SSR getSSRFontStyles() reader landed on
different copies — the reader returned an empty array even though the
loader's pushes succeeded — and the collected <style data-vinext-fonts>
block never made it into the HTML head.

Back every piece of mutable state (counters, injection-tracking Sets,
SSR collection arrays) with a Symbol.for slot on globalThis, the same
pattern navigation.ts already uses for its layout segment context.
This collapses every module copy onto a single shared store so writes
from one copy are visible to reads from another.

Production builds were unaffected because the build plugin inlines
_selfHostedCSS into each font-loader call, so the runtime cycles
through different code paths that don't depend on cross-copy state
visibility.

* fix(font-local): share SSR collection state via globalThis

The font-local shim had the same multi-copy bug as font-google-base:
Vite's multi-environment dev mode can load the shim more than once in
the same worker, so each module copy got its own classCounter,
injection-tracking Sets, and SSR collection arrays. The localFont()
call site and the SSR getSSRFontStyles()/getSSRFontPreloads() readers
could land on different copies, and two copies both minting
__font_local_0 would collide.

Back every mutable binding with a Symbol.for slot on globalThis
(namespaced vinext.fontLocal.*). The counter is reassigned rather than
mutated in place, so it is boxed in a shared { value } object instead
of a bare alias.

Also tighten the globalThis augmentation in both font shims from a
loose [k: symbol]: unknown index signature to the named-key style used
by navigation.ts, removing the per-site casts.

Adds a regression test that creates a genuinely fresh second module
copy via vi.resetModules() and asserts SSR style/preload visibility
and class counter continuity across copies.

* fix(font-local): stabilize class identities across environments

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 22:03:57 +01:00
James Anderson 71dbb45ca8 fix(pages): isolate on-demand revalidation requests (#2495)
* fix(pages): pin revalidate loopback origin

* test(pages): cover production revalidation origin

* fix(pages): preserve internal revalidation boundaries

* fix(pages): preserve on-demand revalidation boundaries

* fix(pages): dispatch Worker revalidation internally

* fix(pages): authenticate revalidation request context

* fix(pages): isolate revalidation transport headers

* test(pages): account for generated route table size

* fix(pages): align on-demand revalidation semantics

* fix(pages): align revalidation response cache parity

* fix(pages): preserve regenerated ISR representations

* fix(pages): align ISR cache representations

* fix(pages): preserve canonical ISR representations

* fix(pages): align cached response parity

* fix(pages): preserve custom App render props

* fix(pages): preserve optional App page props

* fix(pages): preserve optional App props on the client

* fix(pages): normalize client App page props

* fix(pages): preserve App data merge semantics

* fix(pages): keep redirect status helper internal

* fix(pages): match Next.js terminal ISR behavior

* fix(pages): preserve custom app error envelopes

* fix(pages): match Next.js dev revalidation semantics

* test(pages): align dev revalidation parity coverage

* chore(pages): remove stale cache helper exports

* fix(cache): preserve explicit no-store context
2026-07-20 18:45:06 +01:00
Boy Steven 03e8ac6be9 fix(server): defer after callbacks until response close (#2649)
* fix(server): defer after callbacks until response close

* fix(server): drain pages after callbacks on response close

* chore(ci): rerun flaky typegen cleanup

* fix(server): await nested after callbacks

* fix(server): preserve after lifecycle parity

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 12:21:37 +01:00
alex f73ae46edf fix(app-router): stream nested loading boundaries (#2641)
* fix(app-router): stream nested loading boundaries

* fix(app-router): preserve nested loading boundary parity

* fix(app-router): keep prefetch helpers internal

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 11:51:56 +01:00
alex 44ca5ab2fa fix: honor custom TypeScript config path (#2633)
* fix: honor custom TypeScript config path

* test(config): clarify native tsconfig fallback
2026-07-17 23:22:35 +00:00
Boy Steven b8651ef7d7 fix(headers): retain mutable cookie metadata (#2636)
* fix(headers): retain mutable cookie metadata

* fix(headers): keep cookie validator private

* fix(headers): align mutable cookie parity

---------

Co-authored-by: James <james@eli.cx>
2026-07-17 13:19:19 +00:00
Boy Steven 640708ae4e fix(shims): preserve response cookie metadata (#2635)
* fix(shims): preserve response cookie metadata

* fix(shims): align response cookie parity

* fix(shims): match response cookie types

---------

Co-authored-by: James <james@eli.cx>
2026-07-17 14:14:03 +01:00
Boy Steven f7e25aeea0 fix(build): support package validation on Windows (#2638) 2026-07-17 11:42:48 +01:00
Boy Steven 06d64c0012 fix(shims): reject invalid NextResponse JSON bodies (#2634) 2026-07-16 15:41:07 +01:00
github-actions[bot] 7eccf8e898 chore: version packages (beta) (#2616)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-16 12:37:23 +00:00
Nathan Nguyen 0252ea1d8a fix(check): exclude test-runner files from app compatibility scans (#2596)
* fix(check): exclude test-runner files from app compatibility scans

vinext check scans test modules and test-runner configuration as though they are bundled into the migrated application. Apps using CommonJS globals only in Vitest files therefore receive unsupported migration issues even when the vinext build succeeds.

Separate the application compatibility candidate set from the general recursive file finder. Exclude test and spec modules plus conventional Jest, Playwright, and Vitest config files, while keeping ordinary runtime config modules visible to import and convention checks.

* ci: rerun performance benchmarks

---------

Co-authored-by: James Anderson <james@eli.cx>
2026-07-15 22:48:49 +00:00
Nathan Nguyen c6f2a877d0 fix(app-router): stream generated metadata after the document shell (#2619)
* fix(app-router): stream generated metadata after the document shell

Dynamic App Router document renders currently await generateMetadata before constructing the page element tree. This delays response headers and the first HTML chunk for streaming-capable clients.

Head resolution coupled metadata and viewport into one awaited result, so the renderer could not suspend only metadata. Start the branches independently, keep blocking callers unchanged, and expose metadata through paired Suspense tag and error outlets. The hidden host wrapper preserves React's shell flush in the presence of hoistable metadata tags.

The focused head test verifies metadata remains pending while viewport resolution completes.

* test(app-router): verify generated metadata follows the production shell

Production coverage only established that HTML eventually arrived, so delaying the first byte until generateMetadata completed remained undetected.

Read the production response incrementally and require the page shell to precede delayed metadata while still asserting the final tags.

* test(app-router): align metadata error coverage with streamed responses

Streaming generateMetadata errors return a recoverable 200 shell before local or global boundaries render after hydration. The compatibility suite incorrectly expected those boundaries and 500 statuses in raw server HTML, which conflicts with Next.js 16.2.7 and fails the integration shard.

Update raw-response assertions to cover shell behavior and add browser coverage for page and layout metadata errors with and without local boundaries.

* fix(app-router): stream metadata during navigation

* fix(app-router): isolate connection probes from streaming metadata

Dynamic metadata prefetches can leave Flight responses open indefinitely when generateMetadata calls connection(). Streaming starts metadata in parallel with page classification, but the speculative probe mutated shared request state and captured the sibling metadata branch.

Run probes in a nested request scope, then propagate dynamic usage and new diagnostic errors back with concurrency-safe rules. This preserves classification while allowing sibling metadata work to complete.

* refactor(app-router): isolate fallback metadata planning

Streamed metadata fallbacks previously configured the general head resolver with traversal flags for boundary repetition, leaf ordering, and viewport suppression. That made fallback policy part of normal metadata resolution and obscured the RSC navigation transport contract.

Build an explicit HTTP-access fallback metadata source plan, then resolve it through the shared ordered metadata merger. Add focused planner coverage and pin delayed navigation redirects to HTTP 200 Flight digest transport.

* fix(app-router): preserve not-found metadata search params

Page-local not-found metadata lost searchParams after deferred metadata called notFound(), so query-derived tags were wrong and search access was invisible to dynamic-usage tracking. Terminal fallback rendering also recomputed the boundary head without the normalized query.

Classify not-found ownership from module identity and tree position, attach query state and its observer only for page-owned conventions, and thread normalized search params through terminal fallback rendering. Cover repeated fallback leaves, observer access, and a production page-local not-found route.

* fix(app-router): release completed connection probes

Async work created inside a speculative connection probe retained the child request store after the probe returned. Because that store still referenced the completed probe, a later connection() call suspended forever.

Restore the child store's currently inherited probe during deterministic cleanup. This preserves nested probe ownership while allowing late continuations to observe the completed scope, with a real AsyncLocalStorage regression covering the lifecycle.

* fix(app-router): preserve deferred metadata cache signals

Deferred metadata dynamic usage can overlap a speculative layout probe. The probe cleared and later consumed the shared request flag, allowing an RSC cache entry to be written even though the completed response was marked no-store.

The layout classifier treated save-and-restore mutation as async isolation. Run probe classification in a child dynamic-usage scope so sibling metadata retains the parent request state, and cover the overlap through the dispatch cache boundary.

* fix(app-router): preserve fallback metadata parity

---------

Co-authored-by: James <james@eli.cx>
2026-07-15 23:13:04 +01:00
James Anderson 0b6faab89d fix(cache): isolate draft route responses (#2591)
* fix(cache): isolate draft route handler responses

* fix(cache): enforce draft policy on route errors

* fix(cache): privatize draft mode transitions

* fix(cache): respect live draft transitions

* fix(cache): discard route cookies on failed handlers

* Reviewed PR #2591: solid fix, no blocks

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* test(cloudflare): use public CDN adapter export

* fix(ci): align Cloudflare fixture lock entry

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-07-15 14:06:00 +01:00
James Anderson 29e6230212 fix(middleware): align unsafe matcher validation (#2599)
* fix(middleware): support mixed and repeating matchers

* fix(middleware): harden matcher validation

* fix(middleware): close matcher parity gaps

* fix(middleware): make matcher analysis deterministic

* fix(middleware): bound matcher sequence ambiguity

* fix(middleware): model matcher shorthand classes

* fix(middleware): reject quadratic matcher sequences

* fix(middleware): flatten matcher sequence wrappers

* fix(middleware): unwrap exact-one matcher repeats

* perf(runtime): lazy-load config matchers

* fix(navigation): preserve rewrite prefetch reuse

* PR #2599 reviewed: no blockers

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* fix(middleware): avoid matcher chunk initialization race

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-07-15 14:01:43 +01:00
James Anderson 82ef911dfd fix(pages): match encoded string static paths (#2629) 2026-07-15 00:26:45 +00:00
James Anderson bda1fb1353 fix(dev): refresh routes after server restarts (#2588)
* fix(dev): refresh routes after server restarts

* fix(dev): stabilize route cache invalidation
2026-07-15 00:44:55 +01:00
Andrew 106398d494 fix(cache): guard 'use cache' key against Cloudflare KV's 512-byte limit (#2606)
* fix(cache): guard 'use cache' KV key against Cloudflare's 512-byte limit

A long dynamic-route slug flows into the 'use cache' KV key via the
serialized args. When the assembled key exceeded Cloudflare KV's 512-byte
key limit, handler.get threw a 414 *before* the wrapped render reached
notFound()/redirect(), masking those control-flow signals and surfacing a
catch-all not-found as a 200 error boundary instead of a 404 (soft-404).

- buildUseCacheKey now hashes the oversized parts (fnv1a64), mirroring the
  ISR cache's guard in isr-cache.ts (buildCacheKey); the readable
  function-scoped prefix is preserved when it fits so distinct cached
  functions never collide.
- handler.get is wrapped so any cache-store failure falls through to fresh
  execution, letting the function's own thrown digest propagate.

Regression tests added in tests/shims.test.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cache): enforce Cloudflare KV key limits

* refactor(cache): keep key hashing in KV adapter

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-07-15 00:37:21 +01:00