36 Commits

Author SHA1 Message Date
Steve Faulkner 4241f5d31b fix: skip page RSC ISR caching for dynamic requests (#629)
* fix: skip page RSC ISR caching for dynamic requests

* test: align page ISR assertions with helper extraction
2026-03-21 12:19:38 -05:00
Nathan Nguyen abb1aa617c fix: eliminate double middleware execution in hybrid app+pages dev mode (#541)
* fix: eliminate double middleware execution in hybrid app+pages dev mode

In hybrid app+pages dev setups, middleware ran twice per App Router
request — once in the connect handler (SSR env) and again in the RSC
entry (RSC env). This caused double side effects for auth checks,
logging, analytics, and any middleware state mutations.

The connect handler now forwards its middleware results to the RSC entry
via an x-vinext-mw-ctx request header containing the response headers,
rewrite URL, and rewrite status. The RSC entry reconstructs _mwCtx from
the forwarded data instead of re-executing the middleware function.

Key details:
- Response headers are deferred (not applied to res) in the connect
  handler when hasAppDir is true, avoiding duplicates. They're applied
  to res only for Pages routes handled directly by the connect handler.
- The RSC plugin constructs its Request from the original HTTP request,
  not from req.url, so middleware rewrites must be forwarded explicitly
  (the connect handler's req.url rewrite is invisible to the RSC entry).
- Pure App Router (no pages/) and pure Pages Router (no app/) setups
  are unaffected — they already use a single middleware execution path.

* test: update entry template snapshot for middleware forwarding

* fix: address code review findings for middleware forwarding

- Guard x-vinext-mw-ctx check with NODE_ENV !== "production" so the
  forwarding header cannot be exploited in production builds
- Replace bare catch {} with error logging and fallback to running
  middleware normally when the forwarded context is malformed
- Filter x-middleware-next and x-middleware-rewrite from forwarded
  header entries to match the RSC entry's inline filtering behavior
- Apply deferred middleware response headers before all three
  proxyExternalRewriteNode call sites (beforeFiles, afterFiles,
  fallback external rewrites)

* test: update entry template snapshot for review fixes

* fix: extract deferred headers helper, clarify flag placement per review

- Extract applyDeferredMwHeaders() helper to deduplicate 6 identical
  inline blocks in the connect handler
- Add comment clarifying that __mwCtxApplied flag placement inside try
  block is intentional — falls back to re-running middleware on error
2026-03-16 09:28:59 +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
Nathan Nguyen 8f8c2b1d99 fix: emit hashChangeStart/Complete and beforeHistoryChange router events (#468)
* fix: emit hashChangeStart/Complete and beforeHistoryChange events

Next.js emits hashChangeStart/hashChangeComplete for hash-only
navigation and beforeHistoryChange before every history.pushState/
replaceState call. Our router emitted neither.

- Hash-only push/replace now emit hashChangeStart before and
  hashChangeComplete after (instead of routeChange* events)
- Normal push/replace now emit beforeHistoryChange between
  routeChangeStart and the pushState/replaceState call
- All events now pass { shallow } as the second argument per Next.js
- Popstate handler also emits beforeHistoryChange
- Updated in both useRouter() hook and Router singleton code paths

* fix: emit full URL in hash change events, not just fragment

When router.push("#section") is called, emit the full resolved URL
(e.g., "/page#section") in hashChangeStart/hashChangeComplete events
instead of just "#section". Matches Next.js behavior where the event
URL includes the pathname.

* fix: handle hash-only popstate and address review feedback

- Detect hash-only back/forward in popstate handler by tracking
  pathname+search across navigations; emit hashChangeStart/Complete
  instead of routeChangeStart/Complete and skip unnecessary page fetch
- Add comment explaining beforeHistoryChange semantics in popstate
  (fires after URL change, emitted for API compatibility)
- Cache resolveHashUrl() result in all 4 hash code paths
- Add beforeHistoryChange negative assertions in hash-only tests
- Add E2E test for hash-only back/forward navigation events

* fix: pass { shallow } to routeChangeError, add goForward hash test

- routeChangeError now passes { shallow: false } to match Next.js
  signature (err, url, { shallow }), completing the { shallow }
  migration across all router events
- Add E2E test for hash-only goForward() popstate detection

* fix: include hash in popstate beforeHistoryChange URL, stabilize forward test

- beforeHistoryChange in popstate now emits appUrl + window.location.hash
  for consistency with push/replace paths that preserve the hash fragment
- Add waitForFunction between goBack() and clear-events in the forward
  test to prevent race between popstate handler and event clearing

* fix: strip basePath from full-path hash URLs, include hash in popstate event URLs

- resolveHashUrl now handles full-path hash URLs (e.g. /base/page#hash)
  by parsing and stripping basePath, not just fragment-only URLs (#foo)
- routeChangeStart and routeChangeComplete in popstate now include
  window.location.hash for consistency with beforeHistoryChange and
  the push/replace code paths

* fix: update _lastPathnameAndSearch after beforePopState check

Move tracker update to after beforePopState confirms navigation
proceeds. Previously, if beforePopState returned false (cancelling
navigation), the tracker was already updated to the new browserUrl,
causing the next popstate to compare against the wrong baseline and
potentially misdetect a route change as hash-only.
2026-03-12 11:33:23 +00:00
Nathan Nguyen 9bd7f811e0 fix: make useParams() reactive on client-side navigation (#489)
* fix: make useParams() reactive on client-side navigation

useParams() was reading a module-level variable directly instead of
subscribing via useSyncExternalStore, so navigating between dynamic
routes (e.g. /posts/1 → /posts/2) wouldn't re-render components.

Two changes:
- useParams() now uses useSyncExternalStore with the shared listener
  system, matching usePathname() and useSearchParams()
- setClientParams() now calls notifyListeners() when params change,
  so the external store knows to re-check snapshots

* chore: fix formatting in test fixture

* fix: stabilize useParams server snapshot and deduplicate subscribe

- Add _EMPTY_PARAMS constant to avoid creating a new {} on every
  getServerSnapshot call (Object.is would never match, causing
  hydration churn)
- Extract getClientParamsSnapshot/getServerParamsSnapshot named
  functions for consistency with usePathname/useSearchParams
- Extract subscribeToNavigation helper to deduplicate the identical
  subscribe callback across all three hooks
2026-03-12 09:23:10 +00:00
Jared Stowell cb2d1c9ed1 fix: Link onNavigate URL resolution for relative hrefs (#483)
* Fix onNavigate relative href report

* Verify onNavigate relative href

* Fix onNavigate URL mismatch

* Review url-utils changes
2026-03-12 08:17:43 +00:00
Jared Stowell 16a14c713b fix: Pages prod config headers and redirects after middleware rewrites (#448)
* Fix headers matching original path

* Review redirect ordering parity

* Fix config redirects order

* Fix dev middleware parity

* Fix dev middleware request parity

* address review: worker header/fallback parity, early-return in attachRouteHandlerMiddlewareContext, cache node request headers, add dev redirect test

* address second-round bonk comments: sanitizeDestination in worker redirect, nodeRequestHeaders comment, app-rsc-entry guard comment, E2E redirect test, updated snapshots

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 11:40:07 +00:00
Nathan Nguyen 47500b180f fix: return route pattern from router.pathname, not resolved path (#463)
* fix: stabilize instrumentation state and hydration E2E tests

Move Cloudflare instrumentation example state from module-level
variables to globalThis so startup and request paths share one owner
across different module instances.

Add waitForHydration() helper to Pages Router E2E tests to eliminate
flaky button-click assertions that raced against React hydration.

* fix: honor the `as` parameter in router.push/replace

The `as` parameter in push() and replace() was silently ignored
(prefixed as _as) in both the useRouter() hook and the Router
singleton. This broke masked URLs and legacy dynamic-route flows.

Extract resolveNavigationTarget() helper that uses `as` when provided,
falling back to resolveUrl(url) otherwise. Apply to all four call
sites: useRouter().push, useRouter().replace, Router.push, Router.replace.

* fix: address PR review feedback

- Add doc comment to resolveNavigationTarget explaining the url/as
  simplification vs Next.js's two-value approach
- Extract waitForHydration to shared tests/e2e/helpers.ts
- Add pathname assertion to verify router.pathname stays as route
  pattern (/posts/[id]) after as-based navigation

* fix: remove pathname assertion that tests unimplemented behavior

router.pathname currently returns the resolved path after client-side
navigation, not the route pattern. The correct behavior (returning
/posts/[id] instead of /posts/42) requires the two-value url/as
approach tracked in #462.

* fix: return route pattern from router.pathname, not resolved path

In Next.js, router.pathname returns the route pattern (e.g.,
"/posts/[id]"), not the resolved path ("/posts/42"). vinext was
deriving pathname from window.location.pathname which gives the
resolved path.

Fix: use __NEXT_DATA__.page (which holds the route pattern) as
pathname, falling back to location.pathname when __NEXT_DATA__ isn't
available. asPath continues to use the resolved browser path.

Closes #462

* fix: set SSR pathname to route pattern to prevent hydration mismatch

All three SSR entry points (dev-server, pages-server-entry,
static-export) were setting pathname to the resolved URL path (e.g.,
"/posts/42") while the client now derives it from __NEXT_DATA__.page
(the route pattern "/posts/[id]"). This caused hydration mismatches
when components render router.pathname.

Fix: use patternToNextFormat(route.pattern) for the SSR context
pathname, matching what __NEXT_DATA__.page already provides.

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 11:00:36 +00:00
Nathan Nguyen 45b72eade5 fix: honor the as parameter in router.push/replace (#453)
* fix: stabilize instrumentation state and hydration E2E tests

Move Cloudflare instrumentation example state from module-level
variables to globalThis so startup and request paths share one owner
across different module instances.

Add waitForHydration() helper to Pages Router E2E tests to eliminate
flaky button-click assertions that raced against React hydration.

* fix: honor the `as` parameter in router.push/replace

The `as` parameter in push() and replace() was silently ignored
(prefixed as _as) in both the useRouter() hook and the Router
singleton. This broke masked URLs and legacy dynamic-route flows.

Extract resolveNavigationTarget() helper that uses `as` when provided,
falling back to resolveUrl(url) otherwise. Apply to all four call
sites: useRouter().push, useRouter().replace, Router.push, Router.replace.

* fix: address PR review feedback

- Add doc comment to resolveNavigationTarget explaining the url/as
  simplification vs Next.js's two-value approach
- Extract waitForHydration to shared tests/e2e/helpers.ts
- Add pathname assertion to verify router.pathname stays as route
  pattern (/posts/[id]) after as-based navigation

* fix: remove pathname assertion that tests unimplemented behavior

router.pathname currently returns the resolved path after client-side
navigation, not the route pattern. The correct behavior (returning
/posts/[id] instead of /posts/42) requires the two-value url/as
approach tracked in #462.
2026-03-11 09:00:27 +00:00
Jared Stowell c4064ebb23 fix: next/form submitter overrides and query-string GET URLs (#426)
* Add submitter handling to form shim

* Update form shim URL validation

* Fix form submit attribute casing
2026-03-11 00:31:52 +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 88ed5550d1 fix: honor beforePopState when useRouter is mounted (#423) 2026-03-10 23:11:53 +00:00
Jared Stowell ec1b9da131 fix: preserve Pages Router query arrays and hash in asPath (#421)
* Handle array query params

* Fix router query merge order
2026-03-10 22:17:36 +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
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 7fdcdd3c69 feat(og): add @next/og / @vercel/og support with Cloudflare Workers compatibility (#356)
* feat(og): add @next/og support with Cloudflare Workers compatibility

- Add vinext:og-font-fix transform plugin that patches @vercel/og/dist/index.edge.js
  at Vite transform time: inlines the fallback font as base64 (avoids fetch() with
  import.meta.url which breaks in workerd) and defers WASM init to first use (avoids
  WebAssembly.instantiate() being blocked in Node.js module runner)
- Add @vercel/og to optimizeDeps.exclude so Vite's esbuild pre-bundler doesn't cache
  the module before the transform hook runs
- Add vinext:og-assets build plugin that copies font/WASM assets to RSC output dir
- Add /app/api/og route to app-router-cloudflare example and app-basic fixture
- Add shared Playwright e2e spec (tests/e2e/og-image.spec.ts) asserting PNG validity,
  correct 1200x630 dimensions, param-driven content, and determinism
- Register og-image.spec.ts in app-router, cloudflare-workers, and cloudflare-dev
  Playwright projects (18 tests total, all passing)

* refactor(og): generalize fetch+import.meta.url asset inlining, drop WASM lazy-init patch

Replace the @vercel/og-specific vinext:og-font-fix plugin with a general
vinext:og-inline-fetch-assets plugin that:

- Matches ANY fetch(new URL('./asset', import.meta.url)).then(res => res.arrayBuffer())
  pattern, not just the hardcoded noto-sans filename
- Resolves each asset relative to the module's on-disk path (via the transform id)
  and inlines it as base64 — works for any font version or additional assets
- Applies to any library using this pattern, not just @vercel/og

Also removes the WASM lazy-init patch: now that @vercel/og is in optimizeDeps.exclude,
the module runs through workerd (not the Node.js pre-bundler cache) where
WebAssembly.instantiate() works fine. The lazy-init workaround was only needed
when the pre-bundler cache bypassed the transform hook.

The og-assets build plugin is updated to only copy resvg.wasm (not the font,
which is now inlined and will no longer appear in the bundle by filename).

* fix(og): inline readFileSync assets from @vercel/og node build

The production RSC bundle resolves @vercel/og under the 'import' export
condition, getting dist/index.node.js instead of dist/index.edge.js.
The node build loads font and WASM via fs.readFileSync(fileURLToPath(
new URL('./file', import.meta.url))), which breaks after bundling because
import.meta.url points to the bundle output file, not the dist/ dir.

Extend vinext:og-inline-fetch-assets with a second regex pattern that
matches readFileSync(fileURLToPath(new URL(...))) and replaces it with
Buffer.from('<base64>', 'base64'), inlining both noto-sans font and
resvg.wasm at transform time. This eliminates the ENOENT errors seen
in the production build CI tests.

* refactor(og): address code review feedback

- Remove Fix 1 (font inlining) from vinext:og-font-patch — it was dead
  code since vinext:og-inline-fetch-assets (earlier in the plugin array)
  already handles the same fetch(new URL(...)) pattern generically. Only
  Fix 2 (yoga WASM extraction) remains in og-font-patch.

- Use split().join() instead of String.replace() in both Pattern 1 and
  Pattern 2 loops of og-inline-fetch-assets. String.replace(string, ...)
  only replaces the first occurrence; split().join() replaces all of them,
  which matters for libraries that may use the same pattern multiple times.

* refactor(og): use replaceAll instead of split().join()
2026-03-08 20:50:10 +00:00
James Anderson a5d65b851f test: add a fixme for middleware invocation count tracking (#350)
* test: add a fixme for middleware invocation count tracking

* test: clarify why registerCalled uses ESM live binding not globalThis
2026-03-08 15:19:54 +00:00
James Anderson 53257a8630 fix: layout not receiving correct params (#348)
* fix: layout not receiving correct params

* fix: layout not getting correct params on 404
2026-03-08 14:59:06 +00:00
James Anderson 4e0294fc4f fix: middleware crash in dev mode with cloudflare plugin (#344)
* test: middleware with cloudflare plugin and routing bypass

* fix: use module runner for pages middleware

* add pages router dev test
2026-03-08 14:04:11 +00:00
James Anderson f27796b922 fix: don't intercept requests in connect handler when cloudflare plugin is present (#343)
* test: assert App Router wins over Pages Router connect handler with cloudflare plugin

* update req.url with url
2026-03-08 10:28:09 +00:00
James Anderson c4c67fa5f4 fix: pages router instrumentation error reporting for api routes (#334)
* test: pages router instrumentation

* fix error handling

* add pages route to app dir

* fix api route type
2026-03-07 23:22:13 +00:00
James Anderson 80acf27933 fix: register instrumentation in rsc entry and put error handler in global scope (#330)
* test: instrumentation

* store onRequestError on globalThis

* add instrumentation registration app-dev-server

* guard against double calling runinstrumentation for appdir
2026-03-07 22:40:27 +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
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
James Anderson cf60485fe3 fix: static-prefix routes not outranking dynamic catch-alls in route scoring (#275)
* 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
2026-03-06 00:25:54 +00:00
James Anderson af30c7438e fix: config redirects not applying to rsc requests (#255)
* 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
2026-03-05 22:41:44 +00:00
Nathan Nguyen 706f40be0d fix: register MDX plugin at top level to fix transform ordering (#160)
* 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>
2026-03-04 22:47:28 +00:00
Steve Faulkner 6f1902201d fix: use exact heading locator in next/dynamic E2E to fix flaky test (#209) 2026-03-01 07:53:54 -06:00
tomasgferreira 3d8aed9868 fix(config): respect has/missing conditions in next.config headers (#48)
* 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>
2026-02-28 16:05:47 -06:00
Zeb Piasecki bcd4bb14cf feat: add CJS support via vite-plugin-commonjs (#198)
* 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>
2026-02-28 15:23:21 -06:00
ubugeeei 2c650ec089 feat: type aware linting with tsgolint (#38) 2026-02-27 07:37:12 +00:00
luo jiyin dff6da26af fix: correct various typos (#114)
* fix: typo filesytem -> filesystem

* fix: typo filesytem -> filesystem

* fix: typo filesytem -> filesystem

* fix: typo Overwite -> Overwrite

* fix: typo hypen -> hyphen
2026-02-26 21:31:20 -08: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
Dane 98b243493f fix: resolve CodeQL security alerts (ReDoS, incomplete sanitization, bad code gen) (#87)
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
2026-02-26 00:18:46 -06:00
Sunil Pai 493a4c1b70 fix: add 'use client' to next/dynamic shim for App Router RSC compatibility (#7)
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.
2026-02-24 11:13:29 -06:00
Steve Faulkner 12fea722b6 Initial public release of vinext 2026-02-24 09:29:39 -06:00