213 Commits

Author SHA1 Message Date
Nathan Nguyen cb66cc793c feat(next-intl): auto-detect config without createNextIntlPlugin (#366)
* fix: resolve NEXT_DATA type conflict with next package types

next/dist/client/index.d.ts declares Window.__NEXT_DATA__ as NEXT_DATA,
which conflicts with our inline type in global.d.ts. The vinext-specific
extensions (__vinext, __pageModule, __appModule) aren't in NEXT_DATA,
causing TS2339 errors.

Extract vinext extensions into a VinextNextData interface and cast at
the two usage sites (client/entry.ts, shims/link.tsx) instead of
re-declaring __NEXT_DATA__ with a conflicting type.

* feat(next-intl): auto-detect next-intl config without createNextIntlPlugin

next-intl's createNextIntlPlugin() crashes in vinext because it calls
require('next/package.json') to check the Next.js version, and next
isn't installed. Instead, vinext now detects next-intl in project
dependencies and registers the next-intl/config alias automatically.

Detection probes i18n/request.{ts,tsx,js,jsx} then
src/i18n/request.{ts,tsx,js,jsx}, matching next-intl's own discovery
order. Explicit aliases from webpack/turbopack config take precedence.

Also adds a targeted warning when config loading fails due to
next-intl/plugin, telling users the wrapper is unnecessary.

Closes #202

* fix: address review feedback

- Update check.ts: next-intl status "partial" → "supported"
- Extend VinextNextData from NEXT_DATA in shims/internal/utils.ts
  instead of duplicating fields
- Add JSDoc note about monorepo resolution walk-up behavior
v0.0.26
2026-03-09 14:24:41 +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 51730b7093 refactor: extract generateRscEntry, generateServerEntry, generateClientEntry into entries/ (#253 batch 2) (#378) 2026-03-09 13:53:06 +00:00
James Anderson 01202a9bf5 refactor: extract generateSsrEntry and generateBrowserEntry into entries/ (#377)
Moves generateSsrEntry (app-ssr-entry.ts) and generateBrowserEntry
(app-browser-entry.ts) out of the 3,215-line app-dev-server.ts into a new
entries/ directory, as described in issue #253.

app-dev-server.ts re-exports both functions so all existing import sites
(including entry-templates.test.ts) continue to work without changes.

All 10 entry-template snapshot tests pass unchanged — generated output is
byte-for-byte identical to before the move.
2026-03-09 13:22:44 +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
James Anderson 86369f0351 perf: parallelise metadata and viewport resolution (#372)
* perf: parallelise metadata and viewport resolution

* fix: propagate page generateMetadata errors to error boundary

When parallelising metadata resolution, layout errors should be
swallowed (.catch(() => null)) but page-level generateMetadata()
errors must propagate so the caller can route them to the nearest
error.tsx or global-error.tsx boundary.

Fixes the regression introduced by the parallelisation PR where
a throwing generateMetadata() on the page module was silently
swallowed instead of triggering the error boundary.

* test: update snapshots

* fix: log layout generateMetadata/generateViewport errors instead of silently swallowing them

* regen snaps
2026-03-09 11:30:24 +00:00
James Anderson 857f8196a2 perf: load instrumentation without blocking isolate initialization (#373) 2026-03-09 10:59:47 +00:00
MD YUNUS 1bba737dc4 refactor: extract shared request handling from server entry points (#258)
* refactor: extract shared request handling into config-matchers and request-pipeline

Deduplicate ~250 lines of inline request-handling logic from the generated
App Router RSC entry (app-dev-server.ts) and Pages Router server entry
(index.ts) by importing from the canonical config-matchers.ts and a new
request-pipeline.ts module.

Changes:
- Create server/request-pipeline.ts with shared utilities:
  guardProtocolRelativeUrl, stripBasePath, normalizeTrailingSlash,
  validateCsrfOrigin, isOriginAllowed, validateImageUrl,
  processMiddlewareHeaders
- app-dev-server.ts: replace 14 inline __-prefixed functions with imports
  from config-matchers.ts (matchRedirect, matchRewrite, matchHeaders,
  isExternalUrl, proxyExternalRequest, sanitizeDestination,
  requestContextFromRequest) and request-pipeline.ts (validateCsrfOrigin,
  validateImageUrl)
- index.ts: replace inline parseCookies, sanitizeDestinationLocal, and
  matchConfigPattern with imports/re-exports from config-matchers.ts;
  replace manual RequestContext construction with requestContextFromRequest()
- Update tests to reference imported function names instead of old inline
  __-prefixed names

Security fixes:
- matchConfigPattern: eliminates chained .replace() divergence (CodeQL
  flagged) in favor of config-matchers single-pass tokenizer
- proxyExternalRequest: uses AbortController+setTimeout (broader runtime
  support) instead of AbortSignal.timeout()
- escapeHeaderSource: uses proper config-matchers implementation instead of
  simplified chained .replace()

Closes #254

t status

* fix: address bonk review - remove dead applyTo param, dangling comment

* fix: remove dangling comments (parseCookies stub, sanitizeDestination stub)

* fix: remove bare block around __tsRedirect, add multiple trailing slash test

* fix: stricter /api prefix check for trailing slash normalization (matches only /api or /api/), add tests for edge cases

* test: update entry-template snapshots after merging main

---------

Co-authored-by: James <james@eli.cx>
2026-03-09 10:28:10 +00:00
Nathan Nguyen 85425c8d53 fix: enforce segment boundary in catch-all pattern matching (#368)
* fix: enforce segment boundary in catch-all pattern matching

`matchConfigPattern('/foobar', '/foo/:path*')` incorrectly matched
because `startsWith('/foo')` doesn't check for a `/` boundary after
the prefix. Add a character check after the prefix to ensure only
exact segment prefixes match.

* test: regression tests for catch-all prefix segment boundary overmatch

---------

Co-authored-by: James <james@eli.cx>
2026-03-09 10:03:09 +00:00
Nathan Nguyen 2b51ca7a85 fix: update stale compat scanner entries (#370)
next/font/local variable mode works (className and variable both
supported) — the scanner still said it was broken.

styled-components and @emotion/react work via useServerInsertedHTML —
the scanner still said the hook wasn't implemented.

Updated both entries from partial to supported with accurate details,
and updated corresponding test assertions.
2026-03-09 09:57:06 +00:00
Nathan Nguyen f783e4fb4d fix(link): use localized href for SPA navigation (#367)
The locale prop correctly affected the rendered <a> href and prefetch
path, but click navigation and the onNavigate callback used the
non-localized resolvedHref. This meant clicking <Link href="/about"
locale="fr"> would SPA-navigate to /about instead of /fr/about.
2026-03-09 09:56:47 +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 405e46af1d fix: preserve middleware redirect headers in production (#365)
* fix: preserve middleware redirect headers (Set-Cookie) in production

middleware.ts correctly collects non-internal headers (e.g. Set-Cookie)
on redirect responses, but three consumers dropped them:

- prod-server.ts: sent only Location header, ignored responseHeaders
- deploy.ts: worker entry sent only Location header
- index.ts: inline runMiddleware codegen didn't collect headers on redirect
  (it already did for next() and rewrite(), just not redirect)

The dev server (index.ts:2934) handled this correctly, making this a
dev/prod parity bug where cookies set during middleware redirects worked
in development but were silently dropped in production.

* test: update entry template snapshot for redirect header fix

---------

Co-authored-by: James <james@eli.cx>
2026-03-09 09:52:45 +00:00
Divanshu Chauhan (divkix) 8816a13d2d fix: guard dev-origin check with NODE_ENV to prevent 403s in production (#362)
`__validateDevRequestOrigin()` is a dev-only cross-origin protection
that was running unconditionally in the generated App Router RSC entry.
When deployed to Cloudflare Workers, it blocked legitimate cross-origin
requests with 403 Forbidden.

Wrap the call site with `process.env.NODE_ENV !== "production"` — the
same pattern already used nearby. Vite statically replaces NODE_ENV at
build time, making the block dead code that gets tree-shaken away.

Closes #361
2026-03-09 09:43:56 +00:00
Divanshu Chauhan (divkix) 1a2fd61092 fix: fail build when cloudflare() plugin is missing (#358)
* fix: fail build when cloudflare() plugin is missing

Upgrade the deploy-time warning to a hard error and add a build-time
check in configResolved that detects a wrangler config without the
cloudflare() plugin registered.

Previously, forgetting to add cloudflare() to vite.config.ts produced
a cryptic "could not resolve virtual:vinext-rsc-entry" error deep in
wrangler esbuild. Now the build fails early with an actionable message
showing exactly what to add.

The validation only fires during builds (not dev) and is skipped when
Nitro is the deployment target.

Closes #325

* refactor: extract shared utilities for cloudflare plugin guard

Address code review feedback on PR #358:

- Extract `hasWranglerConfig(root)` utility from `detectProject()` inline
  check, reuse in both `deploy.ts` and `index.ts:configResolved`
- Extract `formatMissingCloudflarePluginError({ isAppRouter, configFile? })`
  shared error formatter used by both build-time and deploy-time guards
- Fix hardcoded `vite.config.ts` references: build-time guard now uses
  `config.configFile`, deploy-time guard uses generic "your Vite config"
- Add 10 unit tests covering both new utilities

* Update packages/vinext/src/deploy.ts

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

---------

Co-authored-by: James Anderson <james@eli.cx>
Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
2026-03-08 22:32:58 +00:00
MD YUNUS e5145bd3f8 test: add snapshot tests for entry template generators (#345)
* test: add snapshot tests for entry template generators

Add 7 snapshot tests covering all 5 virtual entry module generators:

App Router (imported directly — already exported standalone):
  - generateRscEntry: minimal routes, with middleware, with config
  - generateSsrEntry
  - generateBrowserEntry

Pages Router (tested via pluginContainer.load on virtual modules):
  - virtual:vinext-server-entry
  - virtual:vinext-client-entry

These snapshots lock down the generated code so subsequent extraction
refactors (moving generators into entries/ modules) can be verified
against a known baseline.

Ref #253

* test: update entry template snapshots after merging main

* test: address bonk review comments on entry-template snapshot tests

- Add comment on PAGES_FIXTURE_DIR warning about snapshot coupling
- Add generateRscEntry snapshot test covering instrumentationPath arg

* test: address new bonk review comments on entry-template snapshot tests

- Add dynamic route fixture (/blog/:slug) to minimalAppRoutes to cover
  dynamic segment serialization in the generated route table
- Add generateRscEntry snapshot test covering globalErrorPath arg

* test: address final bonk review comments

- Add allowedOrigins and allowedDevOrigins to the 'with config' test
  case to cover CSRF validation and dev origin checking code paths
- Replace as any cast with proper type guard for pluginContainer.load()
  result

* test: update snapshots after merge with main

Merge upstream/main which includes f1e98b9 (don't clear RSC context
before lazy stream is consumed). Update 5 snapshots to reflect the
replaced setHeadersContext/setNavigationContext(null) calls with
comments explaining lazy stream context lifecycle.

* test: add metadata routes test, enrich route fixture with loading/error/template/notFound

* test: fix snapshot portability - apply stabilize() to all App Router tests, update snapshots after merge

---------

Co-authored-by: James <james@eli.cx>
2026-03-08 22:12:50 +00:00
James Anderson a47d0817bb fix(mdx): skip ?raw and other query imports in vinext:mdx transform (#357)
@mdx-js/rollup strips the query before matching the file extension,
so it would compile 'foo.mdx?raw' as MDX and return compiled JSX
instead of the raw file text. The proxy now returns early when the
id contains '?', matching Vite's own convention for query imports.

Adds two regression tests: one unit test that reproduces the bug
inline (guard vs no-guard with a mock delegate), and one that
asserts the real vinext:mdx plugin returns undefined for query-param ids.
2026-03-08 21:38:01 +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
Stephen Zhou d00e126eb1 fix: align next/constants shim (#58)
* fix: align next/constants shim

* update

* test

* update

* Update lock

---------

Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
2026-03-08 19:06:54 +00:00
Georgiy Slobodenyuk 8a4929dd63 feat: support experimental.serverActions.bodySizeLimit from next.config (#338)
* feat: support experimental.serverActions.bodySizeLimit from next.config

* fix: address PR review feedback for parseBodySizeLimit

- Accept bare number strings (e.g. "1048576") as bytes, matching Next.js bytes library behavior
- Add tb and pb unit support
- Throw on zero/negative values to match Next.js validation behavior
- Remove unreachable type guard (value is always string at that point)
- Update tests: add tb/pb/bare-number/validation cases, fix "10tb" assertion

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

* fix: warn on invalid bodySizeLimit string instead of silently falling back

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 19:02:14 +00:00
Stephen Zhou 4f8650f1e2 fix: load next.config with runnerImport (#37)
* fix: load next.config with runnerImport

* update comment

* update

* Fix
2026-03-08 18:59:12 +00:00
MD YUNUS faca3dd663 fix(cli): ensure type:module before Vite loads vite.config.ts (fixes #184) (#197)
Projects that lack "type":"module" in package.json caused Vite to bundle
vite.config.ts through esbuild's CJS path, producing a .vite-temp/*.mjs file
that contains a CJS require() for @cloudflare/vite-plugin. On Node 22 in CI
(GitHub Actions ubuntu-latest), require()-ing an .mjs file is not supported
and the build fails with:

  Error: Dynamic require of "…/@cloudflare/vite-plugin/dist/index.mjs" is
  not supported

Fix: call ensureESModule(root) + renameCJSConfigs(root) in both the dev() and
buildApp() CLI commands, before Vite is loaded. This mirrors what vinext init
already does, but applies lazily for projects that were set up manually or
before the init step was introduced.

- renameCJSConfigs renames postcss.config.js / tailwind.config.js / .eslintrc.js
  etc. to .cjs first, to avoid breakage in those files after ESM mode is enabled
- ensureESModule adds "type":"module" to package.json
- Both are no-ops when the project is already correctly configured
- A warning is emitted when a change is made, pointing users to vinext init
- 9 new unit tests cover the full scenario including multi-file rename

Fixes #184
2026-03-08 17:10:11 +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 2fde25365f fix: report App Router render errors to instrumentation (#353)
* fix: report app router render errors to instrumentation

* refactor: reuse request pathname in render error handlers
2026-03-08 15:47:20 +00:00
Nathan Nguyen 032a2afb41 fix: preserve next.config plugin aliases for next-intl (#342)
* fix: preserve next.config plugin aliases

* refactor: probe wrapped webpack config once

* fix: tighten next.config alias warnings
v0.0.25
2026-03-08 15:30:05 +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
MD YUNUS 3a4ef70c25 fix: document ESM resolution behavior for validator and similar packages (#189) (#193)
Vinext now automatically detects installed packages with broken ESM
resolution and adds them to resolve.noExternal for both the rsc and
ssr environments. This fixes 'Cannot find module' errors when using
packages like validator that have extensionless internal ESM imports.

Root cause: Vite externalizes dependencies for SSR by default, but
some packages use extensionless imports (e.g. ./util/assertString)
that fail under Node.js strict ESM resolution. Additionally, Vite 7's
Environment API means ssr.noExternal only affects the ssr environment,
not the rsc environment where server components run.

Changes:
- Add KNOWN_PROBLEMATIC_ESM_PACKAGES list (validator, date-fns)
- Add collectNoExternalPackages() to merge user config with auto-detected packages
- Propagate noExternal to both rsc and ssr environments
- Add validator ecosystem test fixture and integration tests
- Add troubleshooting documentation

Fixes #189
2026-03-08 11:14:46 +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
Nathan Nguyen 2f21bb3a7d fix: pass NextFetchEvent as second argument to middleware (#340)
* fix: pass NextFetchEvent as second argument to middleware

All 3 middleware invocation sites were calling `middlewareFn(nextRequest)`
with only one argument. The `NextMiddleware` type expects `(request, event)`
but `NextFetchEvent` was never instantiated or passed. Any middleware using
`event.waitUntil()` would throw at runtime because `event` is `undefined`.

Fix: instantiate `NextFetchEvent({ page })` at each call site, pass it as
the second argument, and drain `_waitUntilPromises` via
`Promise.allSettled()` (fire-and-forget) after middleware returns.

Call sites fixed:
- `packages/vinext/src/server/middleware.ts` (shared runner, used by prod)
- `packages/vinext/src/index.ts` (Pages Router dev codegen)
- `packages/vinext/src/server/app-dev-server.ts` (App Router dev codegen)

* address review: add drainWaitUntil(), wire ctx.waitUntil() in Workers entry, fix async test

* fix deploy tests: update assertions for ctx-aware fetch and runMiddleware signatures

* address review: fix misleading comment, update JSDoc, use queueMicrotask in test

* pass undefined explicitly to runMiddleware in prod-server (no ctx on Node.js)

---------

Co-authored-by: James <james@eli.cx>
2026-03-08 10:01:47 +00:00
Jared Stowell 397a8dceda Fix fetch cache key collisions for Request and FormData bodies (#332)
* Fix fetch cache key collisions for Request and FormData bodies

* Add additional tests

* Address codex feedback by reading incrementally with early exit on content-length check + regression test

* Add regression test for already-consumed body

* Harden fetch cache keying for Request form bodies + tests

* Preserve FormData insertion order + add file name/type into cache key payload + tests

* Fix PR feedback + additional regression tests
2026-03-08 09:34:32 +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
Jared Stowell 8a5192d1cf Fix unstable_cache undefined cache (#339) 2026-03-08 08:58:17 +00:00
Jared Stowell 49689537d0 Fix and fully implement allowedDevOrigins (#337) 2026-03-08 08:56:15 +00:00
Divanshu Chauhan (divkix) 6762472e70 fix: normalize same-origin absolute URLs for client-side navigation (#336)
* fix: normalize same-origin absolute URLs for client-side navigation

When <Link href="https://example.com/path"> is used and example.com
matches the current origin, vinext now does SPA navigation instead
of a full page reload. This matches Next.js behavior.

Adds toSameOriginPath() utility that extracts the local path from a
same-origin absolute URL. Applied consistently across:
- link.tsx handleClick() and prefetch paths
- navigation.ts navigateImpl() (App Router)
- router.ts push()/replace() (Pages Router hook + singleton)

Closes #335

* fix: handle protocol-relative URLs in toSameOriginPath()

Use window.location.origin as base for `//` URLs so they resolve
correctly for same-origin detection, while keeping `new URL(url)`
(no base) for http/https URLs to preserve existing behavior.

Addresses Copilot review comment on PR #336.

* Update tests/link.test.ts

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

---------

Co-authored-by: James Anderson <james@eli.cx>
Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
2026-03-08 08:55:07 +00:00
17hz d471a52ed8 fix: stop stripping credential headers from rewrite proxy requests (#277)
* fix: stop stripping credential headers from rewrite proxy requests

Rewrite destinations are typically the user's own backend services,
not untrusted third-party origins. Stripping cookie, authorization,
x-api-key, and proxy-authorization headers breaks authentication
for proxied API requests.

Next.js does not strip these headers for rewrites either — it
forwards them as-is to the destination.

* test: update proxy tests to assert credential headers are forwarded

Update tests to match new behavior where credential headers (cookie,
authorization, x-api-key, proxy-authorization) are forwarded through
the rewrite proxy, matching Next.js behavior. x-middleware-* headers
are still stripped.
2026-03-07 23:31:50 +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 cb2bbc65b6 test: app router instrumentation with cloudflare plugin (#333) 2026-03-07 23:04:41 +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
James Anderson a7e68260f5 chore: add note about PLAYWRIGHT_PROJECT in AGENTS.md (#331) 2026-03-07 22:08:28 +00:00
James Anderson 3e0983178f fix: restrict bonk commands to repo members, collaborators, and owners (#327) 2026-03-07 17:32:22 +00:00
James Anderson c9b0a32856 fix: suppress Invalid hook call warning during layout pre-render probe (#324)
* fix: suppress Invalid hook call warning during layout pre-render probe

Layout components that call use(promise) trigger React's dev-mode
'Invalid hook call' warning when invoked outside React's render cycle.
The layout pre-render loop calls LayoutComp() directly to probe for
redirect()/notFound() throws — the same pattern the page pre-render
already handles with a console.error suppressor.

Wrap the layout pre-render loop in a try/finally that silences the
expected warning, matching the existing suppression on the page probe.

* refactor: use AsyncLocalStorage to scope hook-warning suppression per-request

Replace the per-request console.error swap/restore pattern with a single
module-level patch gated behind an AsyncLocalStorage store. Each pre-render
probe (layouts and page) now runs inside _suppressHookWarningAls.run(true)
so the suppression is scoped to that specific async execution context.

This eliminates the race window where concurrent requests could have one
request's suppression filter silently drop real errors from another.
2026-03-07 17:15:26 +00:00
James Anderson b1ce55d0c4 feat: add global.d.ts for window.__VINEXT_* and globalThis.__VINEXT_* globals (#323)
* feat: add global.d.ts for window.__VINEXT_* and globalThis.__VINEXT_* globals

Declares all 23 __VINEXT_* runtime globals in a single ambient declaration file
(packages/vinext/src/global.d.ts), eliminating the scattered (window as any) and
(globalThis as any) casts throughout the shims and server code.

* feat: declare window.__NEXT_DATA__ in global.d.ts, remove remaining (window as any) casts

- Add typed Window.__NEXT_DATA__ declaration covering all fields read by
  router.ts, link.tsx, and entry.ts (props, page, query, isFallback,
  locale/locales/defaultLocale, __vinext, __pageModule, __appModule)
- Remove all (window as any).__NEXT_DATA__ casts in router.ts (×3),
  link.tsx (×1), and entry.ts (×1)
- Drop the win = window as any alias in navigateClient() — it was only
  needed to write __NEXT_DATA__ which is now typed on Window
- Remove redundant export {} at the bottom of global.d.ts; the
  import type { Root } at the top already makes the file a module
2026-03-07 17:12:42 +00:00
Nathan Nguyen 15a4a45df9 test: add unit tests for url-safety isDangerousScheme (#317) 2026-03-07 17:12:21 +00:00
Nathan Nguyen c1ceef2aaf test: add unit tests for Pages Router api-handler (#318)
Cover body parsing (JSON, form-urlencoded, plain text, empty, malformed),
cookie parsing (single, multiple, values with =, missing header),
req/res extensions (status chaining, json, send for objects/strings/numbers/null,
redirect with default 307 and custom status codes), query string and dynamic
param merging (including array promotion for duplicate keys), MAX_BODY_SIZE
enforcement (413 for >1 MB), and error handling (missing default export,
non-function export, handler throws, ssrFixStacktrace integration).
2026-03-07 17:11:56 +00:00
Nathan Nguyen 048c49f98d test: add unit tests for next/document shim (#319)
Cover Html, Head, Main, NextScript, and default Document with
renderToString assertions for attributes, children, placeholder
content, and correct nesting order.
2026-03-07 17:10:56 +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