30 Commits

Author SHA1 Message Date
Webdesign29 7d0203d421 fix: RSC compatibility for dynamic() and layout segment context (#466)
* fix: RSC compatibility for dynamic() and layout segment context

Three related fixes for React Server Component environments:

1. **dynamic.ts: Remove "use client" and add RSC async path**

   The `"use client"` directive forced `next/dynamic` into a client
   component boundary, but `dynamic()` should work in server components
   too. In the RSC environment, `React.lazy` is not available (the
   `react-server` condition exports a stripped-down React). Added a
   runtime check: when `React.lazy` is not a function, use an async
   server component pattern instead (the RSC renderer natively supports
   async components).

   Also switched from destructured imports (`lazy`, `Suspense`,
   `useState`, `useEffect`) to `React.lazy`, `React.Suspense`, etc.
   to avoid importing names that don't exist under the `react-server`
   condition.

2. **layout-segment-context.tsx: Remove "use client"**

   This module is imported directly by the RSC entry. The `"use client"`
   directive created a client component boundary that breaks the RSC
   rendering pipeline. `getLayoutSegmentContext()` already returns
   `null` when `React.createContext` is unavailable (RSC), and the
   `LayoutSegmentProvider` gracefully falls back to passing children
   through unchanged.

3. **app-rsc-entry.ts: Wrap route handler params with makeThenableParams**

   Next.js 15+ changed route handler params to be async (Promises).
   Route handlers that `await params` crash when params is a plain
   object. `makeThenableParams()` wraps the object so it's both a
   Promise and has synchronous property access.

* chore: fix formatting and update entry-templates snapshots after merge with main

* fix: restore 'use client' in layout-segment-context to fix useSelectedLayoutSegment(s)

Removing 'use client' caused LayoutSegmentProvider to run in the RSC
environment where React.createContext is undefined. getLayoutSegmentContext()
returned null, the provider became a no-op, and useSelectedLayoutSegments
always returned [] instead of the actual segments.

The 'use client' boundary is required so the RSC bundler renders this
component in the SSR/browser environment where createContext works.

* test: add regression tests for RSC dynamic() and route handler await params

Add two regression tests for the fixes in PR #466:

1. Route handler await params (Next.js 15 async params pattern):
   Tests /api/catch-all/[...slugs] which uses `await params` in the
   handler — verifies that makeThenableParams() correctly wraps params
   so both await and direct property access work.

2. dynamic() in RSC (async component path):
   Adds a pure server component fixture that uses dynamic() without
   "use client". In the RSC environment React.lazy is unavailable, so
   dynamic() must fall back to the async component pattern. Tests that
   the dynamically-loaded component renders in the HTML output.

* fix: address bonk review comments on dynamic.ts RSC async path

- Add comments explaining LoadingComponent is intentionally ignored in
  the RSC async fallback path (parent Suspense boundaries handle loading
  states; error handling defers to nearest error boundary)

- Add comment explaining the 'as unknown as ComponentType<P>' cast is
  safe because the RSC renderer natively supports async components, but
  TypeScript's ComponentType<P> doesn't account for async return types

- Add unit tests for the AsyncServerDynamic path by stubbing React.lazy
  to undefined (simulating a react-server condition without lazy).
  Verifies: displayName, async resolution, bare component exports,
  and that LoadingComponent is correctly ignored.

Note: React 19.x exports React.lazy from the react-server condition,
so typeof React.lazy !== 'function' does not trigger in current React.
The AsyncServerDynamic path is defensive forward-compatibility code.
The unit tests stub React.lazy to exercise it directly.

* chore: remove unused vi import from dynamic.test.ts

* docs: correct inaccurate React.lazy/RSC comments throughout

React 19.x exports React.lazy from the react-server condition, so the
typeof React.lazy !== 'function' guard does not trigger in current
React. The AsyncServerDynamic path is defensive forward-compatibility
code only — it does not execute today.

Update all comments that incorrectly claimed React.lazy is unavailable
in the react-server environment:

- dynamic.ts: top-level doc comment and guard comment
- dynamic-rsc.tsx fixture: comment explaining the RSC path
- rsc-dynamic/page.tsx fixture: explain which path actually executes
- nextjs-compat/dynamic.test.ts: clarify that the integration test
  exercises LazyServer + Suspense, not AsyncServerDynamic

---------

Co-authored-by: James <james@eli.cx>
2026-03-28 15:19:50 +00:00
Divanshu Chauhan (divkix) 1cd17b5b69 fix: usePathname() returns "/" during SSR of "use client" page components (#689)
* fix: usePathname() returns "/" during SSR of "use client" page components (#688)

The navigation shim uses a registration pattern where navigation-state.ts
calls _registerStateAccessors() to upgrade navigation.ts from module-level
state to ALS-backed state. This only updates the specific module instance
that imported navigation-state.ts.

In Vite multi-environment dev mode, "use client" components can end up with
a separate module instance of navigation.ts (due to pre-bundling or different
resolver chains). That instance never receives the ALS-backed accessors and
falls back to _serverContext (null), causing usePathname() to return "/".

Fix: navigation-state.ts now also stores the accessor functions on globalThis
via Symbol.for("vinext.navigation.globalAccessors"). The default accessors in
navigation.ts check this global before falling back to module-level state.
This ensures all module instances can reach the ALS-backed state regardless
of which instance _registerStateAccessors was called on.

On the browser (where navigation-state.ts is never imported), the global key
does not exist, so the fallback to module-level _serverContext is preserved —
which is the correct behavior for client-side hydration.

Closes #688

* refactor: deduplicate global accessor key and tighten types

- Export GLOBAL_ACCESSORS_KEY from navigation.ts and import in
  navigation-state.ts to prevent silent drift if the Symbol string
  ever changes
- Add satisfies constraint on the accessor object so shape mismatches
  are caught at compile time
- Assert warm-up fetch status in test to surface fixture setup failures
2026-03-26 12:31:03 -05:00
Steve Faulkner 3545fb5faa refactor: extract app page boundary render runtime (#637)
* refactor: extract app page boundary render runtime

* test: harden navigation noindex assertions
2026-03-21 22:23:48 -05:00
James Anderson a86b67dff4 feat: full next/compat/router support and fix vinext check scanner for shimmed modules (#584)
* feat: full next/compat/router support and fix check scanner for shimmed modules

- Export NextRouter interface from shims/router.ts so compat-router.ts can use it
- Add NextRouter | null return type to useRouter() in shims/compat-router.ts
- Add next/compat/router, next/form, next/web-vitals, next/constants to IMPORT_SUPPORT in check.ts so vinext check no longer flags them as unsupported
- Add integration fixture pages and components for next/compat/router in both pages-basic and app-basic fixtures
- Add integration tests in tests/pages-router.test.ts and tests/nextjs-compat/hooks.test.ts
- Add unit tests in tests/check.test.ts covering all four newly-registered imports

* test: update entry-templates snapshots for compat-router-test fixture page

* remove type assertion

* fix: add declare module for next/compat/router to fixture next-shims.d.ts files

* fix: add declare module for next/compat/router to internal next-shims.d.ts

* pin to vp setup 1.0.0
2026-03-19 08:47:40 +00:00
Nathan Nguyen f3c02b84a0 test: add production coverage for global-error compat (#556)
* Add production coverage for global-error compat

* Address global-error review feedback

* docs: update tracker notes for tests #3 and #4 to reflect production preview coverage
2026-03-16 11:19:40 +00:00
Nathan Nguyen 847260ccde fix: align next/headers cookie path semantics (#555)
* Align next/headers cookie path semantics

* Address cookie path review feedback
2026-03-16 11:15:35 +00:00
Stephen Zhou 62910e80e9 chore: move to vp pack, merge vitest config (#550)
* chore: migrate to vp pack

* No copy

* Move test

* Update tsconfig
2026-03-15 11:58:42 +00:00
Stephen Zhou c17d6941be chore: migrate to vite plus (#535)
* chore: migrate to vite plus

* Disable typeAware and typeCheck

* Update CI

* Fix CI

* Fix test

* Clean

* Run test with vp

* Try revert

* react: false In test

* Fix test

* Revert "Try revert"

This reverts commit 009da10473.

* Update

* Update

* Try revert ci changes

* revert

* Run vp migrate

* Disable typeAware and typeCheck for now

* Better resolve for test

* Use vp dev instead of vite

* Update expect

* Fix NormalizeManifestModuleId

* Try increase timeout

* Update to use vp

* Try new check

* Bring back npx vp

* Migrate CI

* Make next-intl resolvable

* Update

* Update

* Update
2026-03-15 10:50:13 +00:00
Nathan Nguyen f12c2005a6 feat: add ISR caching for App Router route handlers (#523)
* feat: add ISR caching for App Router route handlers

Route handlers with `export const revalidate = N` now get server-side
ISR caching (MISS/HIT/STALE with background regeneration), matching
the existing page ISR behavior. Previously only Cache-Control headers
were emitted — the handler re-executed on every request.

- Add `__isrRouteKey` helper (suffix "route") for cache key construction
- ISR cache READ before handler execution (HIT skips handler entirely)
- ISR cache WRITE on MISS via `response.clone()` + `waitUntil`
- STALE serves stale data and triggers background regen with clean
  context (synthetic request, empty headers/cookies)
- Guards: production-only, GET/HEAD only, skips when handler is dynamic
  (uses headers()/cookies()), skips when handler sets own Cache-Control,
  filters Infinity from revalidateSeconds
- Reuses existing CachedRouteValue (kind: "APP_ROUTE") and KV serialization

* chore: update entry-templates snapshots for route handler ISR

* test: add auto-HEAD ISR cache test for route handlers

* fix: review findings — force-dynamic guard, consistent header filtering in cache writes

* test: poll for ISR background regen instead of fixed sleep

Replace fixed 1000ms sleep with a polling loop (up to 5s) to wait
for the background regen to complete. The fixed sleep was flaky
because regen timing varies with system load. Also bumps the stale
wait from 1500→2000ms for slow CI.

* fix: capture request.url before regen closure to avoid pinning Request

Hoist request.url into a local const (__revalUrl) before the
background regeneration closure. This lets the original Request
object (with its body stream and headers) be GC'd sooner on
memory-constrained Workers isolates.

* fix: address remaining review findings

- Hoist url.searchParams into __revalSearchParams before the regen
  closure so the outer URL object can be GC'd during background regen
  (same class of fix as the request.url hoisting)
- Add test for force-dynamic + revalidate route handler guard
- Add comment noting order-dependent test cache state
2026-03-14 12:48:40 +00:00
Sékou Dayifourou KEITA d29ee0eebb fix(route-handlers): align Allow and default-export behavior with Next.js (#497)
* fix: ensure route handlers send correct Allow header

* Inline route handler Allow helpers in RSC entry

* fix: refresh entry snapshots and fmt

* fix(route-handlers): align Allow and default-export behavior with Next.js

* fix(route-handlers): align default-export behavior with Next.js per review

Follow-up for PR #497 review feedback (requested changes).

Changes:

- stop throwing on default export in route handlers

- log dev-only warning when default export is detected

- ignore default export for dispatch so default-only handlers return 405

- keep sorted Allow on implicit OPTIONS and no Allow on 405

- update integration/e2e tests, snapshots, and tracking notes
2026-03-13 12:16:46 +00:00
Divanshu Chauhan (divkix) 9d0fc0d2fb fix: bind iterator methods on request headers proxy (#480) 2026-03-12 07:46:53 +00:00
James Anderson 5b60a70f94 test: headers() and cookies() in server actions and route handlers (#473)
* test: add regression tests for headers() and cookies() in server actions and route handlers

* test: address review comments on action-headers tests

- Remove ghost test that always passed via 404 bailout
- Remove unused getHeaderFromActionSync fixture export
- Fix extractReturnValue to parse full first line instead of greedy {.+} match
- Fix action ID extraction to match by export name instead of array index
- Add comment clarifying why the route handler test is distinct from existing ones

* test: address second round of review comments

- Use fetchHtml helper for page fetches (consistent with other nextjs-compat tests)
- Clarify page.tsx comments: wrapper closures discard return values, named
  export tests are needed for value assertions
2026-03-11 18:07:54 +00:00
Nathan Nguyen b87ab02d58 feat: metadata parity for appLinks, iTunes, Twitter player/app cards (#452)
* feat: add metadata support for appLinks, iTunes, and Twitter player/app cards

Add four missing Next.js metadata features to the Metadata type and
MetadataHead renderer:

- appLinks: renders al:* property tags (ios, iphone, ipad, android,
  windows_phone, windows, windows_universal, web)
- itunes: renders apple-itunes-app name tag with app-id and app-argument
- twitter player cards: renders twitter:player, twitter:player:stream,
  twitter:player:width, twitter:player:height
- twitter app cards: renders twitter:app:name/id/url per platform
  (iphone, ipad, googleplay)

Includes fixture pages and tests ported from Next.js test suite.

* chore: format metadata files

* fix: resolve twitter player/app and appLinks URLs against metadataBase

* fix: un-nest twitter app card name/url checks to match Next.js behavior

In Next.js, the name, id, and url checks in the twitter app card
platform loop are independent. Our implementation nested name and url
inside the id check, so a platform without an id wouldn't get name or
url tags. This un-nests them to match Next.js parity.

Also adds the missing twitter:app:name:ipad test assertion.

* Update packages/vinext/src/shims/metadata.tsx

---------

Co-authored-by: James Anderson <james@eli.cx>
2026-03-11 17:40:18 +00:00
Jared Stowell 0775ce55bb fix: return ReadonlyURLSearchParams from useSearchParams (#424)
* Update navigation search params

* Fix server readonly cache scope
2026-03-10 23:56:11 +00:00
Jared Stowell 3a5fabe7ec fix: next/headers readonly semantics and legacy sync compatibility (#402)
* Add headers/cookies legacy tests

* Fix headers sync error handling

* Fix headers sync guards

* bonk

* Update packages/vinext/src/shims/headers.ts

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>

* bonk

* Clarify dynamic error comment

---------

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: James <james@eli.cx>
2026-03-10 20:21:55 +00:00
Jökull Sólberg Auðunsson 70ff9d5788 fix: handle single object for openGraph.images and twitter.images (#411)
* fix: handle single object for openGraph.images and twitter.images in metadata shim

Next.js allows `openGraph.images` and `twitter.images` to be a single
object (e.g. `{ url, width, height }`) in addition to a string or an
array. The metadata shim only handled string and array cases, causing
"imgList is not iterable" when a single object was passed.

Normalize single objects to arrays before iterating. Also update the
Metadata type to reflect the full range of accepted shapes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fmt

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-03-10 15:19:54 +00:00
Nathan Nguyen 0db5c8d99b fix: avoid shared cache headers for dynamic GET handlers (#381) 2026-03-09 17:41:22 +00:00
James Anderson 764a496ce7 add oxfmt formatter (#380)
* add oxfmt formatter: config, scripts, CI, editor setup, docs

* rebuild lockfile

* fix: add Format to required checks list, remove dead ignore pattern

* run fmt

* add format to agents.md again
2026-03-09 14:56:14 +00:00
James Anderson 23a5cb2ceb refactor: delete app-dev-server.ts, point all callers at entries/ directly (#253 batch 3) (#379)
* refactor: delete app-dev-server.ts and update all references to entries/ directly

The file was already a pure re-export shim with no logic of its own.
Update every import/dynamic-import site to point straight at the
individual entry modules:

  entries/app-rsc-entry.ts    ← generateRscEntry, AppRouterConfig
  entries/app-ssr-entry.ts    ← generateSsrEntry
  entries/app-browser-entry.ts ← generateBrowserEntry

Sites updated:
  packages/vinext/src/index.ts
  tests/entry-templates.test.ts
  tests/app-router.test.ts
  tests/shims.test.ts

Also update stale comments in:
  server/middleware-codegen.ts, server/request-pipeline.ts,
  server/instrumentation.ts, shims/metadata.tsx,
  tests/rsc-streaming.test.ts, tests/nextjs-compat/rsc-context-lazy-stream.test.ts,
  examples/app-router-cloudflare/instrumentation*.ts

* docs: update app-dev-server.ts references in markdown files to entries/
2026-03-09 14:11:07 +00:00
James Anderson 10cc5a2968 fix: pass parent promise to generateMetadata() for ancestor metadata access (#376)
* fix: pass parent promise to generateMetadata() for ancestor metadata access

Implements Next.js's eager-execution-with-serial-resolution pattern for
generateMetadata(): all calls kick off concurrently but each segment's
parent promise resolves serially (root → leaf), so `await parent` inside
a child's generateMetadata correctly gets the accumulated ancestor metadata.

Fixes #375

* regen snaps

* fix: address review comments — remove dead array, pass searchParams to generateMetadata

- Remove unused layoutParentPromises[] array (dead code pointed out in review)
- Pass searchParams to page's resolveModuleMetadata() — the URL query string was
  previously always passed as undefined, so generateMetadata() could never read
  searchParams from the URL
- Reuse the already-computed spObj for pageProps.searchParams to avoid double iteration
- Add 2 regression tests for searchParams in generateMetadata

* regen snaps

* regen snaps

* fix: pass actual route params to layout generateMetadata() in fallback render

renderHTTPAccessFallbackPage was always passing {} as params to
resolveModuleMetadata(), so layouts with generateMetadata({ params })
would get undefined for all dynamic route segments (e.g. params.slug)
when rendering not-found/forbidden/unauthorized fallback pages.

Fix: use opts?.matchedParams ?? route?.params ?? {} (same expression
already used for the layout component props just below) so that
generateMetadata() in fallback layouts receives the real matched params.

Adds two regression tests to not-found.test.ts: hit an invalid slug that
triggers notFound() and assert the title contains the actual slug value.
Also updates entry-templates snapshots.

* fix: forward params to resolveModuleViewport in renderHTTPAccessFallbackPage

generateViewport({ params }) in a layout wrapping a not-found/error
boundary now receives the actual route params instead of an empty object.
Mirrors the same fix already applied to resolveModuleMetadata.

Adds generateViewport export to the layout-params-notfound fixture and
two regression tests confirming the themeColor includes the real slug.
2026-03-09 12:37:36 +00:00
Nathan Nguyen 7405d88af4 fix: draftMode() must mark dynamic usage to prevent caching (#369)
draftMode() reads the __prerender_bypass cookie, which is
request-specific data. Without calling markDynamicUsage(), a Server
Component branching on draftMode().isEnabled could have its output
cached by ISR — serving draft content to regular users or stale
content to draft users.

headers() and cookies() already call markDynamicUsage(); draftMode()
was the only request-reading API that didn't.
2026-03-09 09:53:28 +00:00
Nathan Nguyen 0d38254134 fix: render global-error.tsx without root layout to avoid double <html> tags (#354)
When a server component, client SSR, or generateMetadata() throws with no
local error.tsx boundary, global-error.tsx renders as the last resort. It
provides its own <html> and <body> (replacing the root layout), matching
Next.js behavior.

Previously, global-error was wrapped inside the root layout in both paths:

1. In-tree ErrorBoundary in buildPageElement — the root layout's <html>
   was already serialized in the RSC stream before the ErrorBoundary
   activated during SSR, producing double <html>/<body> tags.

2. renderErrorBoundaryPage — explicitly wrapped the error component with
   layouts regardless of whether it was a local error.tsx or global-error.

Fix:
- Remove global ErrorBoundary from buildPageElement (it can't work for
  SSR because the RSC stream already contains the layout output).
- Add it conditionally for RSC requests only (client-side navigation
  needs it for error recovery after hydration).
- In renderErrorBoundaryPage, track whether the resolved component is
  global-error and skip layout wrapping when it is.

Strengthen test assertions to match Next.js behavior:
- Assert HTTP 200 (error boundary "handles" the error)
- Assert global-error.tsx content renders (data-testid, error message)
- Assert exactly one <html> and one <body> tag per response
2026-03-08 17:03:07 +00:00
James Anderson ed263df6a8 fix: nav context hydration mismatch (usePathname/useSearchParams React error) (#355)
* fix: nav context hydration mismatch

* fix: address bonk review — add __VINEXT_RSC_NAV__ type, serialise searchParams as entry pairs

- Add __VINEXT_RSC_NAV__ global type declaration to global.d.ts with
  searchParams typed as [string, string][] (array of entry pairs)
- Change generateSsrEntry() to serialise searchParams via
  [...navContext.searchParams.entries()] instead of Object.fromEntries(),
  preserving duplicate keys (e.g. ?tag=a&tag=b)
- Update tests to assert the new array-of-pairs shape; new URLSearchParams()
  on the client correctly handles both single and duplicate keys
2026-03-08 16:54:40 +00:00
James Anderson f1e98b9ae5 fix(app-router): don't clear RSC context before lazy stream is consumed (#352)
* fix(app-router): don't clear RSC context before lazy stream is consumed

* fix(app-router): remove premature context clears from server action and intercepting route RSC branches

Same bug as fixed for renderHTTPAccessFallbackPage/renderErrorBoundaryPage: context
was cleared immediately after renderToReadableStream() returned, before the stream
was lazily consumed by the client. Async server components running during stream
consumption would see null context and fail.

Two additional instances fixed:
- Server action RSC re-render branch (after action.apply())
- Intercepting route RSC response branch

The getAndClearPendingCookies() / getDraftModeCookieHeader() calls in the server action
branch are intentionally preserved — they read synchronously from context set during
action execution (not during stream rendering) and must run before the response is sent.

Context cleanup is handled naturally by the AsyncLocalStorage scope from
runWithHeadersContext() unwinding after all async continuations complete.
2026-03-08 16:08:45 +00:00
Nathan Nguyen 5d595e08cb fix: rewrite useSelectedLayoutSegment(s) to use context-based child segments (#341)
The previous depth-based approach sliced the pathname by a numeric URL
depth, which broke when route groups were present — they don't appear
in URLs but Next.js includes them in the segments array. This also
meant dynamic params were returned as raw URL strings rather than
resolved values matching the route tree.

Replace `layoutSegmentDepths: number[]` with two new fields:
- `routeSegments: string[]` — filesystem segments from app/ root
- `layoutTreePositions: number[]` — directory depth per layout

Each layout now receives pre-computed child segments through React
context (including route groups, with dynamic params resolved to
actual values and catch-all segments joined with "/").

Additional fixes found during code review:
- Pass matched params to error/fallback rendering paths so dynamic
  segments resolve correctly during error page rendering
- Skip optional catch-all segments when param is empty/undefined
- Extend routeSegments for parallel slot sub-routes
- Update stale type declaration in next-shims.d.ts

Ported from Next.js: test/e2e/app-dir/hooks/hooks.test.ts
https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/hooks/hooks.test.ts
2026-03-08 09:09:28 +00:00
Nathan Nguyen a78266481b feat: support export const revalidate on App Router GET route handlers (#320)
* chore: add .worktrees/ to gitignore

* feat: support `export const revalidate` on App Router GET route handlers

Read the route segment config `revalidate` from route handler modules
and set `Cache-Control: s-maxage=N, stale-while-revalidate` on GET
responses, matching Next.js behavior. Only applies when the handler
does not set its own Cache-Control header.

Unskip two E2E fixme cases and add vitest coverage.

* fix: guard revalidate=0 and add proper test fixtures

- Skip Cache-Control injection when revalidate is 0 (means "never cache")
- Add /api/custom-cache fixture that sets its own Cache-Control header
- Add /api/no-cache fixture with revalidate=0
- Fix "does not override" test to use a fixture that actually sets Cache-Control
- Add revalidate=0 test case
2026-03-07 17:10:25 +00:00
Nathan Nguyen 8b356e4497 test: use metadata API in app-basic root layout (#316) 2026-03-07 15:14:33 +00:00
James Anderson 8cd07c29d9 fix: override next.config.js headers with middleware headers (#268)
* implement dangerous.middlewareHeadersOverrideNextConfigHeaders from opennext

* fix: preserve multi-value set-cookie in RSC middleware header merge, separate timing comment

* fix: accumulate Vary header in RSC middleware merge to preserve RSC, Accept

* Update packages/vinext/src/server/app-dev-server.ts
2026-03-07 15:05:02 +00:00
Sunil Pai 0e750f7934 Security hardening: redirect sanitization, header stripping, ALS isolation tests, and safer command execution (#105)
* Sanitize redirects/headers and use execFileSync

Replace execSync calls with execFileSync (pass argv arrays) to avoid shell interpolation and adjust package-install logic to split package manager and args. Sanitize redirect destinations in SSR paths to prevent protocol-relative open-redirects and use sanitizeDestinationLocal for Next.js redirects. Ensure middleware receives a cleaned pathname by stripping internal .rsc suffix before creating NextRequest and add a test for this behavior. Strip sensitive headers (cookie, authorization and x-middleware-*) from proxied external requests and add a test. Document cache key hazards for auth headers in fetch-cache, add a security note for dangerouslySetInnerHTML in head SSR, and set the Draft Mode cookie to include Secure in production (with tests for production/dev behavior).

* Rename middleware header to x-custom-middleware

Change middleware header name from x-middleware-test to x-custom-middleware across fixtures and tests, and clarify server comment about stripping x-middleware-* headers from responses (reserved for internal routing signals). Files updated: packages/vinext/src/server/prod-server.ts (comment tweak), tests/fixtures/pages-basic/middleware.ts (header set), and tests/pages-router.test.ts (expectations and test titles/comments). This ensures tests reflect the new public header name while preserving internal x-middleware-* semantics.

* Normalize leading slashes and strip headers

Normalize redirect/rewrite destinations by collapsing any leading slashes or backslashes to a single "/" to avoid protocol-relative redirects and handle browser-backslash behavior. Strip additional sensitive headers (x-api-key, proxy-authorization) from proxied requests and ensure all x-middleware-* internal headers are removed from responses (broadened from x-middleware-request-*). Update dev/server sanitization logic accordingly. Update tests and fixture middleware to use shortened x-mw-* header names and add tests covering backslash normalization.
2026-02-26 08:50:12 +00:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00