The 25-line __ensureInstrumentation block in entries/app-rsc-entry.ts was
module-level state plus idempotent init logic embedded in a generated entry
template. This is the canonical 'imperative shell with mutable bookkeeping'
pattern — it never belongs in codegen.
Lift it to a new server/instrumentation-runtime.ts module exporting
ensureInstrumentationRegistered(instrumentationModule). The generated RSC
entry now only:
1. Imports the helper (conditional on instrumentationPath).
2. Calls await ensureInstrumentationRegistered(_instrumentation) in the
request handler.
All bookkeeping (initialized flag, shared promise, prerender skip) lives in
the typed helper, making it unit-testable and keeping the entry template thin.
New tests cover idempotency, concurrent racing, prerender skipping, and
graceful handling of modules without register/onRequestError.
Refs:
- Next.js instrumentation docs: https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
- Next.js source: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/dev/next-dev-server.ts (instrumentation boot)
- Next.js source: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-relayer.ts (onRequestError global wiring)
App Router route discovery currently shares a single module with route caching and request-time matching. That makes discovery changes harder to review and increases the chance that graph construction work collides with generated entry changes.
The route graph construction types and discovery helpers now live in app-route-graph.ts, while app-router.ts remains the public cache and trie matching wrapper. A direct graph-builder test covers pages, route handlers, layouts, and inherited parallel slots at the new boundary.
* refactor(build): move route classification injection behind a typed planner
The Vite generateBundle hook currently owns stub detection, module-graph classification, replacement generation, and source-map invalidation. That makes generated-entry behavior harder to test without running the full plugin lifecycle.
The hook now adapts Rollup chunks and module info into a route classification injector. The injector plans the patch, validates drift cases, preserves Layer 1 priority over module-graph results, and keeps source-map invalidation explicit.
Tests cover scan-phase skips, stub drift errors, debug reason patching, seed-module absence, and the existing integration path.
* refactor(build): tighten classification stub validation
* fix(rsc): exclude client shims from dep optimization
App Router dev config currently excludes the vinext package but not its client shim subpaths. @vitejs/plugin-rsc tracks package client references by the original bare import source, so optimized vinext/shims/* modules can lose the matching export metadata and crash client-package proxy loading.
Add a shared optimizeDeps exclude set for vinext's known RSC client shims and merge it into the top-level, RSC, SSR, and client optimizer configs while preserving existing user and server-external excludes.
* Update rsc-client-shim-excludes.ts
Co-authored-by: James Anderson <james@eli.cx>
* Update rsc-client-shim-excludes.ts
Co-authored-by: James Anderson <james@eli.cx>
* Update build-optimization.test.ts
Co-authored-by: James Anderson <james@eli.cx>
---------
Co-authored-by: James Anderson <james@eli.cx>
* fix: next/image only fire onError and onLoad once per src per mount (#990)
Use ref-based dedup to prevent onLoad and onError from firing multiple
times for the same image src across React re-renders. Ported from the
upstream Next.js fix in vercel/next.js#93209.
- Destructure onError from props (previously fell through rest spread)
- Add lastLoadedSrcRef/lastErrorSrcRef to guard against duplicate fires
- Pass onError/onLoad to all 4 render paths (was missing from UnpicImage)
- Add SSR tests covering all render paths and verifying no DOM attribute leaks
The dedup prevents infinite re-render loops when user code calls setState
inside onError/onLoad, matching Next.js behavior.
* fix: add "use client" directive to next/image shim
useRef is a hook requiring client rendering context. Without "use client", RSC executes useRef() and throws "useRef is not a function", breaking create-next-app CI.
* fix: move resolveImageSource above handler closures for readability
Adding or removing any JavaScript or TypeScript file under app currently invalidates the App Router route cache and sends a full reload. That is wrong for colocated components and helpers because the App Router only treats specific file conventions and metadata files as route structure.
The watcher was using the broad page extension matcher for app files, which confused ordinary project files with route-affecting files. Add a dev route-file predicate aligned with vinext route and metadata discovery, then use it before invalidating the generated RSC entry and root params modules.
Tests cover route convention files, metadata files, private folders, sibling directory prefix safety, and colocated non-route files.
* fix: replace relative shim imports with bare specifier vinext/shims/X (#1001)
Internally, server modules imported their own 'use client' shims via
relative paths (e.g. from "../shims/slot.js"). @vitejs/plugin-rsc's
packageSources tracking only fires for bare specifier imports, so
relative shim imports took a broken fallback path generating absolute
filesystem paths that fail against the package's exports field.
Changes:
- 67 relative shim imports across 34 source files rewritten to
"vinext/shims/<name>"
- Added .tsconfig paths mapping so TypeScript resolves shim types to
source (avoids type mismatch between dist and source module graphs)
- Added vitest project-level resolve aliases for vinext/shims -> source
(prevents separate module graph instances in unit tests)
- Added package.json exports for ./dist/shims/*.js as a safety net
- Added regression test (tests/shim-imports.test.ts) that asserts no
relative shim imports exist in source
* fix: address review feedback for relative shim imports (#1006)
- Convert missed side-effect import in dev-server.ts to vinext/shims/router-state
- Extend regression test regex to catch bare side-effect import statements
- Remove redundant ./dist/shims/*.js exports from package.json
* replace test with oxlint plugin
---------
Co-authored-by: James <james@eli.cx>
* fix: pumpReader now re-throws errors to prevent incomplete ISR cache data (#1002)
* test: add regression test for pumpReader error propagation (#1002)
App Router error boundaries treated the stored error value itself as the presence flag. That makes thrown values like undefined, null, 0, an empty string, or false indistinguishable from an empty boundary state, so the children can render again instead of the segment fallback.
The boundary state now stores either null or a wrapper containing the thrown value, and route error component typing admits unknown values to match React and Next.js behavior. The regression coverage checks both the state transition contract and a client component route that throws every falsy literal.
* refactor(metadata): share route pattern helpers
Metadata route handling repeated the same dynamic segment normalization and pattern matching in several places. That made the file-convention metadata path harder to review and kept generic routing behavior inside metadata-specific modules.
Move route pattern parsing, matching, and dynamic segment filling into a normal routing module. Reuse it from metadata route build data, file-based metadata, metadata route response matching, and app RSC intercept matching, then consolidate duplicated icon descriptor normalization in the metadata server and shim code.
Add focused route-pattern tests and keep the existing metadata and shim tests covering consumer behavior.
* fix(routing): preserve prototype-named route params
The shared route pattern matcher used a normal object for matched params, unlike the previous metadata and App RSC matchers. A route parameter named __proto__ could mutate the params object's prototype instead of being stored as an own value.
Restore a null-prototype params map and add regression coverage for single and catch-all prototype-named route params.
* fix(routing): preserve literal intercept target segments
* refactor(app-router): extract app page dispatch
App Router page requests now enter a shared dispatch module after generated route matching. The generated RSC entry still describes route modules, app-shape callbacks, and request-local values, but cache reads, static generation context, dynamic param checks, intercept responses, and lifecycle rendering move into normal typed code.
This keeps page orchestration out of codegen without changing the observable App Router behavior covered by the integration suite.
* chore: rerun ci
* fix: support enablePrerenderSourceMaps config with default true (#960)
Adds the `enablePrerenderSourceMaps` config option matching Next.js behavior.
Defaults to `true` so prerender stack traces resolve through sourcemaps to
original source files. Error results in prerender output now include full
stack traces when enabled. Node.js sourcemap support is enabled before the
prerender phase in both `cli.ts` (build) and `deploy.ts` (deploy).
Config parity: silently accepts the option; recognized as "supported" in
`vinext check` output.
* refactor: address review comments for enablePrerenderSourceMaps (#960)
- prerender.ts: simplify getErrorMessageWithStack and fix comment
(V8's Error.stack always prepends message, remove startsWith guard)
- cli.ts: add Error.stackTraceLimit = 50 after setSourceMapsEnabled
- deploy.ts: add Error.stackTraceLimit = 50 after setSourceMapsEnabled
- next-config.test.ts: move enablePrerenderSourceMaps tests to own describe
block and remove redundant explicit-true test
* fix(prerender): simplify getErrorMessageWithStack and add test coverage
Simplify the error stack handling in getErrorMessageWithStack by removing
the redundant nullish coalescing with empty string. The expression
`err.stack || err.message` handles all cases correctly.
Add test coverage proving that enablePrerenderSourceMaps: true (default)
includes stack traces in prerender errors. New test fixture page
`error-throw.tsx` throws during render to exercise the error path.
* fix(prerender): address review feedback - update snapshot, add config comment
- Regenerate entry-templates.test.ts.snap after adding error-throw fixture page
- Add comment clarifying enablePrerenderSourceMaps default in prerender test
* feat(config): add enablePrerenderSourceMaps config option
Adds support for the experimental.enablePrerenderSourceMaps config
option that Next.js 15+ introduced for debugging prerender issues.
Also includes merged changes from main:
- hashSalt: support experimental.outputHashSalt + NEXT_HASH_SALT env
- swcEnvOptions warning: warn about unsupported SWC polyfill option
All 75 next-config tests passing.
Closes#960
* fix: unstable_io() returns hanging promise during prerendering (#972)
During prerendering (static export, --prerender-all, TPR), unstable_io()
must return a hanging promise to suspend React past the IO boundary.
Previously it always returned a resolved promise, matching only the
browser/client implementation.
Changes:
- Add makeHangingPromise utility (never-resolving promise, rejects on abort)
- Define typed WorkUnitStore discriminated union in workUnitAsyncStorage
- unstable_io() branches on work unit store type per Next.js's io.ts
- Set prerender work unit store in RSC entry when VINEXT_PRERENDER=1
- Add tests for prerender, request, cache, and abort scenarios
* fix: add knip entries for prerender-work-unit and work-unit types (#972)
* fix(prerender): address PR review feedback for unstable_io work unit store
- AbortController now properly aborted in .finally() to prevent listener leaks
- Add optional route field to PrerenderStore for better error messages
- Pass pathname as route in app-rsc-entry handler
- Use actual route instead of hardcoded 'unknown' in unstable_io()
- Add explicit return in default branch of cache.ts for clarity
- Remove premature knip entry for make-hanging-promise.ts
- Replace V8-internal .status check with Promise.race pattern in tests
* fix(app-rsc-entry): place route option in correct location
Move { route: __pathname } to __runWithPrerenderWorkUnit() call
instead of incorrectly appending to _handleRequest closing brace.
Fixes parse error introduced in 035a54f.
Refs #979
* fix: address prerender hanging promise review feedback
- Suppress unhandled rejection when signal already aborted in makeHangingPromise
- Add lazy pathname extraction via getter function in runWithPrerenderWorkUnit
- Add listener array cleanup after abort in makeHangingPromise
- Add test for unhandled rejection suppression on early-return path
- Update entry template snapshots for lazy route getter
* fix: resolve merge conflicts with main for prerender work unit
- Take main's refactored app-rsc-entry.ts as base (extracted runtime primitives)
- Apply prerender-work-unit wrapping around handler function
- Update tests to match refactored code structure (delegated helpers)
- Update entry-templates snapshots for new generated code structure
* fix: resolve merge conflicts with main for test files
- Keep main's tests/app-router.test.ts (already has correct refactored tests)
- Update entry-templates snapshots to include prerender-work-unit generated code
* perf: fuse RSC stream tees reducing allocs from 3 to 2
ISR cache path teed the RSC stream twice (once for capture,
once for embed). Fuse into single tee: sideStream serves both
embed transform and raw byte accumulation via createRscEmbedTransform.
Tee 1 (capture) and Tee 2 (embed) now share one tee. Tee 3 (HTML
cache) remains separate — it operates on post-SSR HTML with a
conditional write decision.
Fixes#981
* fix: address PR review feedback for fused RSC stream tees (#981)
- Fix AppPageSsrHandler type to include sideStream and capturedRscDataRef
- Clear rawChunks after getRawBuffer() to reduce peak memory usage
- Remove misleading no-op null assignment in handleSsr else branch
- Guard await null in ISR cache write to prevent undefined in cache
* fix: repair CSP nonce handling and update snapshots for #981
- Fix renderAppPageHtmlStream to always pass ssrOptions (including scriptNonce)
to handleSsr, instead of conditionally when sideStream is defined. This fixes
CSP nonce regressions in dev mode.
- Update 6 snapshot occurrences in entry-templates.test.ts.snap to match the
updated __revalCapturedRscRef.value access pattern with null safety.
Fixes CI failures for the fuse RSC stream tees PR.
* fix: support experimental.outputHashSalt config and NEXT_HASH_SALT env var (#754)
Add support for Next.js experimental.outputHashSalt config option and
NEXT_HASH_SALT environment variable. When set, the salt is mixed into
content-addressed build output filenames via Rollup/Rolldown's
output.hashSalt option, producing different hashes without modifying
source code — useful for cache-busting after CDN poisoning.
Config resolution: concatenates config salt + env var salt (matching
Next.js behavior: config first, then env). When neither is set, no
salt is applied and behavior is unchanged.
* ci: re-trigger CI (Windows create-next-app flake, STATUS_STACK_BUFFER_OVERRUN)
* fix: use augmentChunkHash for outputHashSalt instead of output.hashSalt
The output.hashSalt option is webpack-specific and is silently ignored by
Rollup/Rolldown. Use the augmentChunkHash plugin hook instead, which is
supported by both bundlers and actually affects chunk content hashes.
Changes:
- Remove hashSalt parameter from getClientOutputConfigForVite()
- Add vinext:hash-salt plugin with augmentChunkHash hook
- Add experimental.outputHashSalt to CONFIG_SUPPORT map
* refactor: simplify hash-salt plugin comments
- Simplify comment about augmentChunkHash (just Rolldown, not Rollup)
- Add scope-gap note about CSS/static asset hashing not being salted
* fix: forward invalid dynamic usage errors on client-side navigations in dev (#958)
Record errors from throwIfInsideCacheScope() on the per-request
context so they survive user-code try/catch and can surface on
client-side client navigations. In dev, wrap the RSC response
body to check for recorded errors after stream consumption and
log them to the terminal.
Ported from Next.js: vercel/next.js@f5e54c0 (#93184)
* refactor: extract _recordInvalidDynamicUsageError helper in server.ts
Extract the repeated ALS lookup + error recording pattern into a
single reusable function _recordInvalidDynamicUsageError(). Replaces
4 inline try/catch blocks in _throwIfInsideCacheScope with calls to
the new helper.
Also adds a comment in app-page-render.ts explaining that HTML path
coverage is intentionally deferred (RSC-nav scope only).
No behavioral changes - same global keys, same error paths.
* fix: add SWR non-blocking guard tests for route handler ISR (#959)
Audit confirms vinext correctly uses ctx.waitUntil() for all background
cache-write and regeneration work — no equivalent of the Next.js
pendingWaitUntil / pipe-readable blocking pattern exists.
## Changes
- Add slow ISR route handler fixture (revalidate=1, 1s handler delay)
- Add response-time assertion to existing STALE test (< 500ms)
- Add new test verifying stale response completes fast despite slow
background regeneration
Ported from Next.js:
test/e2e/app-dir/use-cache-swr/use-cache-swr.test.ts
* docs: clarify porting attribution in SWR ISR tests
Adjust comments to note test pattern is adapted from Next.js
"use cache" SWR tests, not a direct translation. The mechanism
differs (export const revalidate vs "use cache").
Draft mode cookies currently use SameSite=Lax in production and clear with Max-Age=0. That diverges from Next.js and can prevent production preview flows from receiving the bypass cookie in cross-site contexts.
Centralize draft-mode cookie attributes in the headers shim, use SameSite=None and Secure outside development, and clear the bypass cookie with an expired date. Add focused compatibility coverage for production enable and disable attributes.
Optional catch-all routes currently materialize the missing segment list as an empty array. Next.js leaves the param key absent when the optional catch-all capture group does not match, so pages and app routes can observe a different params shape at the zero-segment path.
The route trie treated zero remaining segments as a captured value. The fix keeps the route match while returning an empty params object for that zero-segment branch, and applies the same normalization to standalone app RSC pattern matching.
Tests cover shared trie matching, app RSC matching, app route discovery, pages route discovery, and hyphenated optional catch-all params.
* refactor(app-router): extract RSC runtime primitives
The App Router RSC entry template currently owns low-level ISR keying, server-action body reads, and RSC error digest behavior. That makes generated code harder to inspect and leaves runtime contracts covered only through template snapshots.
Move those primitives into typed server modules and keep app-rsc-entry.ts focused on app-shape imports, route tables, and request-specific wiring. Add focused helper tests for slot normalization, streamed action body limits, production digest behavior, and generated-entry delegation.
* fix(app-router): address RSC primitive review feedback
* fix(app-router): return action-not-found for stale server actions
Unknown or stale Server Action ids currently fall into the generic action error path and return 500. That hides deployment skew from the client and misses the Next.js action-not-found response contract.
The action loader is an external runtime boundary, so the helper now treats loaded actions as unknown until narrowed, maps recognized missing-action failures to the 404 x-nextjs-action-not-found protocol, and keeps unrelated loader failures on the existing 500 reporter path.
Tests cover fetch actions, progressive form decode misses, missing exports, generic loader failures, and the App Router integration response.
* chore: rerun ci
* fix: accept experimental.swcEnvOptions config without erroring (#774)
Next.js added experimental.swcEnvOptions for SWC preset-env polyfill injection.
vinext uses Vite for transforms, so polyfill injection is not applicable, but
the config key should be accepted without errors. Add:
- CONFIG_SUPPORT entry in check.ts (unsupported with detail)
- Warning during config resolution in next-config.ts
- Tests for analyzeConfig detection and dot-notation matching
* refactor: align swcEnvOptions messaging and add test coverage
- Change runtime warning from "not yet supported" to "not applicable"
- Update CONFIG_SUPPORT detail for consistent messaging
- Add test coverage for swcEnvOptions warning in next-config.test.ts
* fix(check): update test assertion for swcEnvOptions message
The CONFIG_SUPPORT message for experimental.swcEnvOptions was updated
to indicate it's not applicable since vinext uses Vite instead of SWC.
This updates the corresponding test assertion to match the new message.
Fixes CI failure in tests/check.test.ts
* fix: add ForbiddenBoundary/UnauthorizedBoundary for HTTP access fallback recovery (#764)
Per-layout forbidden/unauthorized boundary files (forbidden.tsx, unauthorized.tsx)
are now discovered by the router and wired into the App Router component tree via
dedicated ForbiddenBoundary and UnauthorizedBoundary client components. Previously,
only NotFoundBoundary existed, causing 403/401 errors thrown during RSC rendering
(e.g. inside Suspense boundaries) to escape into the generic error shell — this
could produce mismatched Flight/HTML data and client-side 'Connection closed' failures.
Next.js upstream: vercel/next.js#92231https://github.com/vercel/next.js/pull/92231
* fix: address review feedback on ForbiddenBoundary/UnauthorizedBoundary (#945)
- Export ForbiddenBoundaryInner and UnauthorizedBoundaryInner for testing
- Strengthen tests to import and call getDerivedStateFromError directly
- Use exact equality instead of startsWith for status code digest matching
- Make forbiddenPaths and unauthorizedPaths non-optional on AppRoute (matching notFoundPaths)
- Fix inaccurate ported-from attribution in test header
* fix: remove duplicate forbidden/unauthorized path iteration and update comment
Removes dead code in app-rsc-entry.ts (lines 185-190) that duplicated the existing import registration loops.
Also updates the stale nesting order comment in app-page-route-wiring.tsx to include Unauthorized and Forbidden boundaries.
* fix: align NotFoundBoundary digest matching and tests with Forbidden/Unauthorized patterns
- Use exact === instead of startsWith in NotFoundBoundary digest check
- Add forbiddenPaths/unauthorizedPaths per-layout test fixtures
- Fix property ordering: singular before plural for all boundary types
* fix: resolve rebase conflicts — align manifest code with main refactor
- Use buildAppRscManifestCode for manifest generation (matching main)
- Remove redundant || [] guards for non-optional forbiddenPaths/unauthorizedPaths
- Fix test fixture indices to match new module import count
* fix: add null guards for notFoundPaths, forbiddenPaths, unauthorizedPaths in manifest
TypeError: route.notFoundPaths is not iterable when routes lack per-layout boundary files.
CI was failing on app-router.test.ts with the error. Added null guards in both registerRouteModules() and buildRouteEntries() to handle undefined arrays.
* fix: reorder route-level boundary nesting for merge with main
Move ErrorBoundary after NotFoundBoundary/ForbiddenBoundary/
UnauthorizedBoundary to match Next.js nesting order (Error >
Unauthorized > Forbidden > NotFound). Resolves merge conflict
with bc70e1c.
* fix: add tests verifying strings thrown in Server Components are not swallowed (#810)
Next.js had a bug where createReactServerErrorHandler returned early
for typeof thrownValue === 'string', swallowing the error before
logging. vinext's rscOnError already handles strings correctly (they
flow through the normal error path and get wrapped into Error objects).
Added:
- Fixture page that throws a string in a Server Component
- Integration test verifying the error boundary renders with the string message
- Runtime test confirming rscOnError returns a valid digest for string throws
- Static test confirming generated code has no early string-return path
Upstream: vercel/next.js@b9ca95c62d
* fix: address review feedback on string error handling tests (#950)
- Move prodRscOnError creation into beforeAll to avoid duplicating new Function construction
- Remove brittle positive assertions (instanceof Error, String(error)) from static test; keep only the negative anti-pattern guard
- Use textContentByTestId helper for integration test precision over loose html.toContain
Move NotFoundBoundary wrapping before ErrorBoundary wrapping at the
route level in app-page-route-wiring.tsx. In Next.js, ErrorBoundary
is outermost relative to NotFoundBoundary. The segment-level ordering
was already correct; only the route-level code was inverted.
metadataRouteSuffix exempted robots and manifest in addition to sitemap,
while getMetadataRouteSuffix only exempts sitemap (matching Next.js).
This was latent because robots/manifest are nestable: false, but the
inconsistency could cause subtle bugs if nestable ever changes.
Remove the extra exemptions so both functions match Next.js exactly.
* fix: support file:// URLs in cacheHandler config path (#728)
- Add cacheHandler and cacheMaxMemorySize to NextConfig type
- Add cacheHandler to ResolvedNextConfig with file:// URL handling
- Create resolveCacheHandlerPathToFilesystem() helper using fileURLToPath()
- Convert file:// URLs from import.meta.resolve() to filesystem paths
during config resolution to prevent broken path concatenation
Matches Next.js commit 9a0214a1 which added the same file:// URL handling
for ESM projects using import.meta.resolve() for the cache handler path.
* fix: drop unused export on resolveCacheHandlerPathToFilesystem
* fix: resolve cacheMaxMemorySize and add cacheHandler tests
- Add cacheMaxMemorySize to ResolvedNextConfig and resolve it from input config,
preventing silent config swallowing
- Add 7 tests for resolveNextConfig cacheHandler: file:// URL resolution,
absolute/relative path passthrough, null/undefined defaults
- Also add cacheMaxMemorySize resolution and default tests
* fix: add experimental.prefetchInlining to compatibility check (#860)
Next.js flipped experimental.prefetchInlining from false to true by default
in PR #92863. vinext does not implement per-segment prefetch inlining — it
uses unified RSC navigation payloads — so no behavioral change is needed.
This adds the config option to the compatibility scanner so vinext check
correctly reports it as supported.
* fix: mark experimental.prefetchInlining as partial, add test
- Change status from supported to partial (per review feedback —
option is recognized but underlying behavior is not implemented)
- Add test case in tests/check.test.ts matching existing
experimental.* config patterns
* fix: report stale ISR background revalidation errors via onRequestError (#775)
Next.js 16 now reports errors during background ISR revalidation of stale
pages via routeModule.onRequestError(). Previously these errors were silently
swallowed (only logged to console). This ensures instrumentation hooks
(Sentry, OpenTelemetry) observe revalidation failures.
- Add errorContext parameter to triggerBackgroundRegeneration() in isr-cache.ts
- Report errors via reportRequestError() with revalidateReason: "stale"
- Update App Router entry template to pass route context to regen callbacks
- Update Pages Router entry template to forward error context through wrappers
- Update dev-server.ts and pages-page-data.ts to pass route context
- Update snapshots and test assertions for generated code changes
* fix: remove dead ISRRegenErrorContext export flagged by knip
* fix: add next/offline shim with useOffline() hook (#701)
Add a no-op shim for next/offline that exports useOffline() returning false.
Registers it in the shim resolution map, type declarations, and import support
check so libraries importing next/offline don't fail. The full offline retry
behavior is deferred until the feature stabilizes upstream.
* fix: address review feedback for next/offline shim
- Add "use client" directive to offline.ts for RSC plugin compatibility
- Add JSDoc block explaining the stub (matching amp.ts pattern)
- Fix test comment to reference PR instead of implementation file
* Apply suggestion from @james-elicx
---------
Co-authored-by: James Anderson <james@eli.cx>
* fix(fonts): make Google font shim registrations stable
Repeated next/font/google calls currently mint counter-based class names and append new SSR style rules. During dev HMR, the app module can re-evaluate while the shim module stays alive, so stale classes and links accumulate and the first emitted :root variable keeps winning.
The bad invariant was treating each runtime call as a new font registration. Use a deterministic identity for each family and options pair, remove the non-Next :root variable rule, and keep variable declarations scoped to the returned variable class.
Regression coverage verifies stable class identities, no :root emission, and no SSR style growth for repeated equivalent calls.
* fix(fonts): address Google font review feedback
The first PR version left two edge cases in the new stable identity path: crafted font family names could still leak unsafe selector characters into class names, and omitted display hashed differently from explicit display: swap even though both generate the same font URL.
Sanitize the readable family segment used in selectors, normalize display to the same default used by buildGoogleFontsUrl, and tighten the CSS variable regression so it checks CSS added by the current call instead of accumulated module state.
* fix(fonts): canonicalize Google font identity hashing
Equivalent Google font loader options could generate the same CSS and font URL while still producing different class identities. That reintroduced duplicate SSR style rules when option arrays used a different order or representation.
The identity hash now canonicalizes unordered option sets, default-style equivalence, and fallback whitespace before hashing. A focused regression test covers reordered weights and subsets, explicit normal style, and trimmed fallback names.
* fix(fonts): normalize default fallback font identities
Omitting next/font/google fallback produced the same CSS as fallback: ["sans-serif"] but still hashed a different raw option shape. That left the most common default call path outside the deterministic identity dedupe.
The identity hash now uses the resolved fallback list and no longer includes the serialized font-family CSS string. Regression coverage verifies implicit and explicit default fallback calls share class and variable identities, and the :root assertion now checks only styles added by its own test.
* refactor(app-router): delegate RSC preload hint normalization
The App Router RSC entry currently owns the React Flight preload hint stream transform inline. That keeps byte-stream buffering logic inside generated code, so the behavior is only indirectly covered by generated-entry snapshots.
Move the stream normalization into a typed server helper and keep the generated RSC entry as a thin wrapper around renderToReadableStream. Add direct coverage for complete lines, split chunks, and final unterminated Flight hint lines.
* test(app-router): cover mixed Flight preload hints
The RSC entry still owned route matching, intercept lookup construction, and dynamic metadata pattern matching inside generated source. That kept request-path behavior in a template string and made direct tests awkward.
Move the matching behavior into a typed server helper, keep route import and manifest emission in the generator, and update entry-template assertions to verify delegation instead of inline matcher code.
Middleware cookies set through NextResponse only updated Set-Cookie, so cookies() in the same request could not observe values written by middleware. External middleware rewrites were normalized to pathname and search, so cross-origin destinations were routed locally instead of being proxied.
Mirror middleware cookie mutations into x-middleware-set-cookie and merge that internal header back into the request cookie store. Preserve cross-origin rewrite URLs through middleware execution and proxy them across app, pages, prod, and deploy request paths, while stripping internal middleware headers from client and upstream responses.
* fix(app-router): scope layout params and propagate layout head errors
Layouts received full route params, layout generateMetadata failures were swallowed, and layout-thrown forbidden()/unauthorized() fell back through the not-found boundary path. Those diverge from Next.js when nested layouts depend on segment params, when head generation fails before page render, or when HTTP access APIs are thrown from a layout.
The generated RSC entry now delegates param slicing, head resolution, and parent access-boundary selection to typed runtime helpers. The helpers keep route-specific imports in the entry while moving the behavioral core into unit-tested modules.
Tests port Next.js layout params, global-error, forbidden, and unauthorized coverage into focused helper and integration regressions.
* test(app-router): cover layout viewport error boundaries
Layout generateViewport() failures now intentionally share the same boundary path as layout generateMetadata() failures. Copilot flagged that this behavior change needed direct coverage.
Add dev and production-preview compat assertions for co-located error.tsx handling and global-error escalation, with fixtures that throw from layout generateViewport().
* chore(app-router): address head review nits
Cached App Router pages only carried implicit revalidatePath tags derived from the resolved URL. That left dynamic routes tagged with concrete values, so pattern-scoped invalidation like revalidatePath("/blog/[slug]", "layout") could miss entries rendered at /blog/hello.
The generated RSC entry now delegates implicit tag construction to a typed helper that combines exact pathname tags with route-segment-derived layout, page, and route handler tags. Focused tests cover dynamic route patterns, route groups, root aliases, and route handler leaves.
Refs cloudflare/vinext#921
App route handlers appended cookies().set() mutations after cookies already present on the returned Response. That made same-name mutable cookies override the Response cookies, which differs from Next.js route-handler semantics.
Next.js treats mutable cookies as fallbacks and reapplies returned Response cookies as final values. Deduplicate vinext pending mutable cookies by name, filter out pending cookies shadowed by the returned response, and then reappend the returned Set-Cookie headers last.
Adds focused helper coverage plus a nextjs-compat fixture that exercises the live App Route path.
Move renderToReadableStream() before buildPagesShellHtml() + clearSsrContext()
so that <Head> and other SSR state collectors (styled-jsx, useServerInsertedHTML)
are populated before they are read. This fixes a race condition where head styles
were silently dropped because they were collected before the page had finished
rendering.
Mirrors Next.js fix: vercel/next.js@9853944
The specific styled-jsx race condition (styledJsxInsertedHTML() called
concurrently with page render via Promise.all) does not exist in vinext because
vinext does not implement a styled-jsx style registry at all. However, the same
class of ordering bug affected vinext's Pages Router prod path where getSSRHeadHTML
was called before rendering completed.