Commit Graph

1668 Commits

Author SHA1 Message Date
github-actions[bot] 3d30a3ca36 chore: version packages (beta) (#2632)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@vinext/cloudflare@1.0.0-beta.3 vinext@1.0.0-beta.3
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
Boy Steven 3a3dfe82d4 test(build): cover extensionless optimized imports (#2650)
* fix(build): resolve extensionless optimized imports

* fix(build): honor Vite extension resolution order

* fix(build): honor environment resolver extensions

* fix(build): preserve Vite directory resolution

* test(build): cover extensionless optimized imports

* test(build): harden extensionless import coverage

---------

Co-authored-by: James <james@eli.cx>
2026-07-21 00:37:45 +01:00
James Anderson ce00c8dbf6 fix: sync middleware matcher fixture lockfile (#2658) 2026-07-20 23:28:30 +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
Christoph Richter bd87926171 docs(cloudflare): drop duplicate rsc() from App Router config examples (#2652)
The Cloudflare App Router `vite.config.ts` examples in README.md and in the
migrate-to-vinext skill both register `@vitejs/plugin-rsc` explicitly. Copying
either verbatim fails the build immediately:

    [vinext] Duplicate @vitejs/plugin-rsc detected.
             vinext auto-registers @vitejs/plugin-rsc when app/ is detected.

vinext has auto-registered the plugin since the `app/` detection landed
(packages/vinext/src/index.ts), and every deployed App Router example
(app-router-cloudflare, app-router-playground, hackernews) omits `rsc()`.
The docs are the stale part.

Both examples now match the working examples, plus a note on the non-obvious
bit: `@vitejs/plugin-rsc` is an optional peer, so it must be installed but not
registered, and `rsc: false` is the escape hatch for owning the registration.

Co-authored-by: piffie <1213363+piffie@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:42:18 +01:00
Jerry Zhao b16b3e8c8b test(app-route-graph): build the dot-directory expectation with canonical() (#2601) 2026-07-20 21:37:33 +01:00
dependabot[bot] 6c33639d4b chore(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#2655)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 21:37:23 +01:00
Boy Steven 2fcc4443cc test(isr): cover production cache lifecycle (#2651)
* fix(isr): preserve cache headers on initial render

* fix(isr): preserve streaming on initial cache misses

* test(isr): run production lifecycle coverage in CI

* test(isr): wait for asynchronous cache writes

---------

Co-authored-by: James <james@eli.cx>
2026-07-20 21:33:50 +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
dependabot[bot] 2d1449deb0 chore(deps): bump zizmorcore/zizmor-action from 0.5.7 to 0.6.0 (#2639)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.7 to 0.6.0.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/192e21d79ab29983730a13d1382995c2307fbcaa...6599ee8b7a49aef6a770f63d261d214911a7ce02)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-18 00:19:00 +01: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
James Anderson 97fd96ff1c feat(web): classify compatibility suite support (#2640)
* feat(web): classify compatibility suite support

* feat(web): add compatibility results table

* feat(web): filter compatibility results table

* fix(web): keep compatibility feature map internal
2026-07-17 14:12:00 +01:00
Boy Steven f7e25aeea0 fix(build): support package validation on Windows (#2638) 2026-07-17 11:42:48 +01:00
Boy Steven abec025162 test(types): run tsc portably on Windows (#2637) 2026-07-16 15:45:56 +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>
@vinext/cloudflare@1.0.0-beta.2 @vinext/types@1.0.0-beta.2 create-vinext-app@1.0.0-beta.1 vinext@1.0.0-beta.2
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 8e55df80ec fix(ci): keep Vite+ installs from dirtying the lockfile (#2631) 2026-07-15 09:57:36 +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
James Anderson 50ac8fe039 fix(pages): isolate static render router state (#2583)
* fix(pages): isolate static render query context

* fix(pages): preserve serialized router hydration state

* fix(pages): stabilize ISR router readiness

* fix(pages): normalize localized hydration pathname

* fix(pages): restore router hydration parity

* fix(pages): align fallback router hydration state

* fix(pages): preserve live fallback browser path
2026-07-15 00:37:06 +01:00
James Anderson eacd44a223 fix(pages): validate redirect destinations consistently (#2586)
* fix(pages): block executable redirect schemes

* fix(pages): keep redirect safety within bundle budget

* test(pages): exercise middleware data redirect blocking
2026-07-15 00:29:28 +01:00
yyh 388144d660 fix(config): keep page extensions out of module resolution (#2594)
* fix(config): keep page extensions out of module resolution

* fix(config): preserve default extension priority
2026-07-15 00:19:51 +01:00
Sakamoto, Kazunori d61ace5bc0 fix(navigation): avoid rewriting URL for history metadata (#2615)
* fix(navigation): tolerate history state writes rejected by the browser

history.pushState/replaceState can throw a SecurityError when document.URL
still contains userinfo credentials (e.g. behind HTTP Basic auth Chromium
strips them from location.href but not from document.URL), in sandboxed or
opaque-origin documents, or when the per-origin call quota is exceeded.

The notify-suppressed history primitives only persist vinext navigation
metadata, so an unguarded call let a hydration-time replaceState failure
escalate to the global error boundary and blank the whole page. Wrap both
primitives in try/catch and log a warning so metadata-write failures degrade
gracefully.

Fixes #2614

* fix(navigation): avoid rewriting URL for history metadata

---------

Co-authored-by: James <james@eli.cx>
2026-07-15 00:04:54 +01:00
Andrew 121290f1e9 fix(og): resolve dot-hash wasm fallbacks (#2608) 2026-07-14 23:57:57 +01:00
James Anderson 8b4b608bd0 docs(router): clarify static param case matching (#2628)
* docs(router): clarify static param case matching

* fix(pages): preserve raw production request paths
2026-07-14 22:38:51 +01:00
James Anderson 4f7eec967e fix(router): avoid repeated App path decoding (#2556)
* fix(router): avoid repeated App path decoding

* test(router): align encoded middleware pathname

* fix(router): match Next encoded path semantics

* fix(prerender): accept canonical encoded app params

* fix(router): preserve encoded interception params

* test(build): tolerate watch output replacement

* test(router): cover repeatedly encoded production paths

* fix(router): preserve action request route identity

* refactor(middleware): remove stale pathname argument

* refactor(router): align fallback interception paths

* refactor(router): derive request path identity from value

* fix(router): preserve encoded route parity

* fix(router): preserve raw config path matching

* fix(router): preserve raw encoded route identity

* fix(router): canonicalize encoded dot segments

* fix(router): restore encoded path parity after main merge

* fix(app-router): exact-match generated params

* fix(app-router): chain generated parent params

* fix(app-router): preserve generated param ownership

* fix(app-router): preserve per-result param ownership

* fix(app-router): preserve params after empty parallel generation

* fix(app-router): chain parallel static params by branch
2026-07-14 22:09:29 +01:00
James Anderson 8c2e4bb119 fix(pages): preserve data route path identity (#2580)
* fix(server): reject ignored controls in Pages paths

* fix(pages): align data path guards across runtimes

* fix(pages): preserve encoded data route params
2026-07-14 21:28:34 +01:00