* fix: static-prefix routes not outranking dynamic catch-alls in route scoring
* share route precedence logic between app and pages router
* fix(routing): static-prefix boost collapsed infix-static ordering to a tie
* extra comment
* fix: config redirects not applying to rsc requests
* fix poorly written test
* add better fix for soft nav url diffs
* escape the .
* fix: strip .rsc from beforeFiles rewrites; fix redirect recursion and double pushState
* fix: register MDX plugin at top level to fix transform ordering
Plugins returned from the config() hook run after vite:import-analysis,
so @mdx-js/rollup transforms never fire. Move MDX to a top-level proxy
plugin with enforce:"pre" that delegates to the lazily-created MDX
instance once config() populates it.
Fixes#77
* address review: add config proxy, remove dead resolveId, fix comment separation
- Replace resolveId proxy with config proxy: @mdx-js/rollup has no
resolveId hook, but its config() hook initializes format-aware
processors with the Vite mode (development vs production). Without
forwarding config(), MDX always compiles in production mode.
- Remove dead resolveId proxy (dead code, @mdx-js/rollup has no such hook)
- Separate MDX proxy comment from vinext:react-canary comment
- Update test to verify config and transform hooks on the proxy
* add E2E test for MDX import transform ordering
A regular page.tsx imports content.mdx as a React component and
renders it. This verifies the vinext:mdx proxy plugin transforms
.mdx files before vite:import-analysis runs (the core fix from #77).
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* 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>
* feat: add CJS support via vite-plugin-commonjs
Add vite-plugin-commonjs to handle require() and module.exports in
both App Router and Pages Router. This enables CJS interop in server
components and pages, including CJS require('server-only') boundary
enforcement by @vitejs/plugin-rsc.
- Add vite-plugin-commonjs as a dependency and register it in the plugin array
- Add App Router fixtures: CJS basic, CJS server-only, CJS server-only
violation (client component importing server-only module)
- Add Pages Router fixture: CJS basic (require/module.exports)
- Add integration tests for both routers (cjs.test.ts)
- Add App Router e2e tests for CJS rendering and server-only boundary
enforcement (tests/e2e/app-router/cjs.spec.ts)
- Add Pages Router e2e test for CJS rendering
(tests/e2e/pages-router/cjs.spec.ts)
* fix: use buildViteConfig() for Pages Router production builds
The Pages Router build path hardcoded plugins: [vinext()] in inline
config, duplicating plugins when a vite.config.ts also registers
vinext(). This caused double-transformation errors during production
builds. Use buildViteConfig() instead, matching the App Router path
which already handles this correctly.
* fix: move server-only-violation fixture to isolated directory
The server-only-violation fixture causes a fatal build error during
production builds of app-basic (the RSC plugin rejects the intentional
violation). This breaks unrelated tests in app-router.test.ts.
Move the fixture to tests/fixtures/app-cjs-violation/ so it doesn't
poison the shared app-basic production build. Remove the E2E test case
since it requires the app-basic dev server; the server-only boundary
enforcement for CJS require() is verified by the RSC plugin's build
validation.
* fix: address review feedback from bonk
- Remove unused components/random.tsx (dead code, Pages Router CJS test
uses pages/cjs/random.ts instead)
- Add trailing newlines to fixture files
- Move commonjs() plugin earlier in array (after tsconfigPaths, before
vinext:config) so CJS->ESM transform runs before directive scanning
- Tighten test assertions: use regex for 'Random:.*4' to be specific
while handling React SSR comment nodes between text and expressions
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
* 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.
Refactor chained .replace() to single-pass tokenizers in pattern matching:
- matchConfigPattern in config-matchers.ts and index.ts
- escapeHeaderSource (new) replaces inline chained escaping in matchHeaders/applyHeaders
- matchMiddlewarePattern in prod server entry template
- matchPattern in middleware.ts
Also:
- escapeHeaderSource correctly handles :param(constraint) patterns
- Fix decodeURIComponent parity gap in index.ts matchConfigPattern
- Fix redundant dot in middleware.ts tokenizer char class
- Add lgtm suppression comments for false positives in test files
- Add unit tests for escapeHeaderSource
When a server component imports next/dynamic, the RSC serializer must
treat it as a client boundary. Without 'use client', the dynamic()
function executes inline in the RSC environment — for ssr:false, this
serializes null into the RSC payload and the client never loads the
component.
Adding 'use client' matches Next.js's App Router behavior where
next/dynamic is a client module. The RSC serializer emits a client
reference, and the client executes the full dynamic logic including
the ssr:false mount-on-client behavior.
Updated test fixtures that called dynamic() from server modules to
include 'use client' — this is required since dynamic() is now a
client export and can only be called from client modules.
Added E2E test for ssr:false from a server component page.