Commit Graph

1688 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>
@vinext/cloudflare@1.0.0-beta.4 vinext@1.0.0-beta.4
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
James Anderson 69f03c871b fix(test): prevent recursive deploy log growth (#2693) 2026-07-24 11:21:45 +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
Nathan Nguyen 391eda0ce9 test(link): isolate asynchronous prefetch setup (#2684)
Link prefetch tests could restore global mocks while detached RSC URL hashing was still in flight, allowing late requests to land in the next test. The dynamic-route case also counted a leaked viewport request as its second fetch.\n\nTrack cache-busting digest promises through teardown and make positive assertions wait for their owned requests. A delayed-digest regression keeps the original race reproducible.
2026-07-23 14:26:29 +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>
@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