* fix: apps with both app + pages router returning 404s
* apply same fix for api routes
* test for pages dir route
* always filter x-middleware headers, and preserve others on redirect
* fix(app-router): avoid React 19 dev Flight decode crash in SSR (#50)
* fix(app-router): avoid React 19 dev Flight decode crash in SSR (#50)
* test(ecosystem): harden fixture cleanup and isolate better-auth signup data
* chore: address review nits on React 19 dev Flight decode fix
- Convert standalone JSDoc block to plain // comment to avoid confusion
with the adjacent createRscEmbedTransform JSDoc
- Add note that VinextFlightRoot returns a thenable (not ReactNode),
relying on React 19's internal thenable-as-child behavior
- Add comment noting the 'Switched to client rendering' assertion
depends on React internal message text
- Clarify error.tsx fixture exists for realistic route structure, not
because the error boundary fires during SSR
- Merge main into branch
---------
Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* fix(rsc): emit actionable hint when non-plain object crosses server-client boundary
When a Server Component passes a class instance, ESM module namespace
object, or null-prototype object as a prop to a Client Component,
React's RSC serializer throws "Only plain objects, and a few built-ins,
can be passed to Client Components from Server Components." In dev mode
this error previously surfaced in the browser with no indication of
which component or prop triggered it, making it hard to debug in large
apps.
Add a console.error in rscOnError that detects this specific error and
prints an actionable guide explaining the four common causes, including
the key ESM vs webpack difference: Vite produces true ESM module
namespace objects (non-serializable), while Next.js webpack produces
plain __esModule objects (serializable) — causing code to silently work
in plain Next.js but correctly fail in vinext.
* test(rsc): add unit tests for rscOnError non-plain object dev hint
Verify that the generated RSC entry code produced by generateRscEntry
includes the dev-mode hint for issue #237. Tests cover:
- Detection string for "Only plain objects" error is present
- Hint is guarded by NODE_ENV !== "production"
- Actionable guidance for module namespace objects is included
- Actionable guidance for class instances is included
- Existing digest return path for navigation errors is unaffected
* fix: escape newlines in rscOnError hint and add runtime tests
- Double-escape \n in the console.error hint strings so the generated
JavaScript output contains valid string literals (the single-escaped
\n was resolved to literal newlines by the template literal, producing
unterminated strings that broke production builds).
- Add explicit 'return undefined' after the hint console.error block.
- Add runtime tests that extract rscOnError from the generated code via
new Function() and verify: digest passthrough for navigation errors,
console.error hint for RSC serialization errors, and undefined return
for generic dev errors.
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* fix: throw on missing middleware/proxy exports instead of failing open
Middleware and proxy files that don't export the expected function
(e.g. misspelled export, wrong named export) were silently skipped,
letting requests through unprotected. This matches Next.js behavior
which throws a ProxyMissingExportError in the same scenario.
Changes:
- proxy.ts files now require 'proxy' or 'default' export
- middleware.ts files now require 'middleware' or 'default' export
- All 3 code paths updated (Pages dev, Pages prod, App Router)
- Added resolveMiddlewareHandler() with file-type-aware validation
- Ported test cases from Next.js proxy-missing-export test suite
- Updated AGENTS.md: added required step to search Next.js tests
Ported from Next.js: test/e2e/app-dir/proxy-missing-export/
https://github.com/vercel/next.js/blob/canary/test/e2e/app-dir/proxy-missing-export/proxy-missing-export.test.ts
* fix: update export precedence test to match Next.js behavior (named > default)
* fix: use ?? instead of || in generated middleware export resolution for consistency
Matches the shared resolveMiddlewareHandler() which uses nullish
coalescing. No behavioral difference since typeof check catches
all cases, but keeps the three code paths consistent.
* fix(config): respect has/missing conditions in next.config headers
Adds has/missing condition support to matchHeaders, wires request context
through dev/prod paths, updates NextHeader types, and adds unit + e2e tests
for header/cookie/query conditions.
* chore: add backward-compat comment for optional ctx in matchHeaders
Addresses review feedback: clarify that when no request context is
passed, has/missing conditions are silently skipped for backward
compatibility with existing callers.
* fix: wire has/missing conditions through redirects and rewrites in Pages Router dev
Pages Router dev was the only path missing has/missing support for
redirects and rewrites. The reqCtx was already being built for headers,
so hoist it and pass it to applyRedirects and applyRewrites too.
Also refactors applyRedirects and applyRewrites to delegate to the
shared matchRedirect/matchRewrite functions, eliminating duplicated
pattern-matching logic (same dedup pattern used for applyHeaders).
---------
Co-authored-by: tomoconstrutor <tomoconstrutor@gmail.com>
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* fix: use static import for renderToStaticMarkup to eliminate dual-import warning
The generated SSR entry imported renderToReadableStream statically from
react-dom/server.edge at the top level, but later used a dynamic
import() for renderToStaticMarkup from the same module. Rollup detects
this mixed static+dynamic import pattern and emits a warning for every
build:
"react-dom/server.edge" is imported as both a static and dynamic
import in the same module
Fix: add renderToStaticMarkup to the existing static import, removing
the redundant dynamic import(). The module was already loaded — the
dynamic import added no lazy-loading benefit since it runs
unconditionally during SSR.
* Remove comment on renderToStaticMarkup import
Removed comment about static import of renderToStaticMarkup.
* Remove unneccesary test
---------
Co-authored-by: Fred K. Schott <622227+FredKSchott@users.noreply.github.com>
Config matchers (redirects, rewrites, headers) now receive the decoded,
normalized pathname so that percent-encoded request paths like /%61bout
correctly match config rules defined for /about.
Both dev and prod servers rebuild the working URL from the decoded
pathname + original query string, ensuring all downstream consumers
operate on canonical paths.
Also adds unit and integration tests for percent-encoded path handling
in config matchers across App Router and Pages Router (dev + prod).
Strip all x-middleware-* prefixed headers from responses in both the
App Router and Pages Router dev servers before sending to clients.
These headers are internal routing signals (x-middleware-next,
x-middleware-rewrite, x-middleware-override-headers, etc.) and should
not be visible to end users.
The prod server already stripped these correctly. This brings the dev
servers into parity.
Wrap all decodeURIComponent calls on user-controlled URL paths in
try/catch to return 400 Bad Request instead of throwing an uncaught
URIError that crashes the Node process.
A single request with a malformed percent-encoded path (e.g. /%E0%A4%A)
could terminate the entire server process. This affected all server
entry points: prod server (App + Pages Router), dev server middleware,
Cloudflare Worker entry, and generated RSC/middleware handlers.
Fixes:
- prod-server.ts: App Router and Pages Router request handlers
- app-router-entry.ts: Cloudflare Worker entry
- app-dev-server.ts: Generated RSC entry handler
- index.ts: Dev server connect middleware + generated middleware runner
+ generated NEXT_LOCALE cookie parser
- middleware.ts: Pages Router dev middleware runner
Includes 11 regression tests across app-router, pages-router, and
features test suites.
* fix: close security gaps in init.ts shell exec and app-dev-server external proxy
Gap 1 (GHSA-w7vc-6jjg-498h): migrate init.ts from execSync to execFileSync
with argument array, matching the pattern already applied in deploy.ts and
cli.ts. Inputs are hardcoded package names so practical risk is near zero,
but this eliminates the last instance of the flagged pattern.
Gap 2 (GHSA-pf96-3m7r-pv3j): harden the inline __proxyExternalRequest in
the generated RSC entry (app-dev-server.ts) to strip Cookie, Authorization,
x-api-key, proxy-authorization, and all x-middleware-* headers before
forwarding to external rewrite targets. Also adds a 30-second
AbortSignal.timeout. This brings the dev-server code path into parity with
the already-fixed proxyExternalRequest in config-matchers.ts.
Also fixes a missed GSP redirect path in dev-server.ts where backslash
normalization was not applied (GHSA-79rw-r8hc-qg8v — the GSSP path was
already committed).
Includes an integration test that verifies credential stripping end-to-end
via a mock HTTP server and a conditional external rewrite in next.config.ts.
* fix: unify isExternalUrl to detect all URL schemes and protocol-relative URLs
All four copies of isExternalUrl (config-matchers.ts, router.ts,
navigation.ts, app-dev-server.ts inline) now use the same RFC 3986
scheme regex: /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('//')
Previously:
- config-matchers.ts and app-dev-server.ts only checked http:// and
https:// (missing // protocol-relative)
- router.ts and navigation.ts checked http://, https://, and // but
missed exotic schemes (data:, javascript:, blob:, ftp:, etc.)
Now all copies are consistent and detect any URL scheme per RFC 3986
plus protocol-relative URLs.
Adds test cases for exotic schemes, protocol-relative, hash-only, and
bare string inputs.
* fix: add shell: true to execFileSync for Windows .cmd file compat
On Windows, package managers (pnpm, npm, yarn) are .cmd batch files.
execFileSync without shell: true cannot execute them. Adding shell: true
lets the OS shell resolve .cmd files while still using the argument array
form for safer argument passing.
* 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.