Commit Graph

1925 Commits

Author SHA1 Message Date
Nicholas Bardy b71ccd27c2 fix: wire ctx.waitUntil for middleware fetch event background tasks (#126)
* fix: pass NextFetchEvent to middleware and bubble up waitUntil promises to Cloudflare Worker ctx

- Instantiates `NextFetchEvent` with `waitUntil` shim and passes to `middlewareFn`
- Extracts `_waitUntilPromises` from the middleware result and attaches them to the `Response` object via `__vinextWaitUntil`
- Updates the generated Cloudflare Worker `fetch` signature to include `ctx: ExecutionContext`
- Iterates over `__vinextWaitUntil` on the final response and delegates to Cloudflare's native `ctx.waitUntil(promise)`
- Enables background tasks (like telemetry and session sync in `@clerk/nextjs`) to survive the worker response lifecycle
- Updates `vinext check` to explicitly report `@clerk/nextjs` as supported
- Adds `app-router.test.ts` assertion to verify `waitUntil` injection

* fix: address review feedback for waitUntil/NextFetchEvent support

- Fix lost waitUntil promises on redirect/custom response early returns
- Settle waitUntil promises in prod-server (Node.js) instead of dropping
- Mark @clerk/nextjs as "partial" until ESM fix lands (clerk/javascript#7954)
- Align __vinextWaitUntil guard in deploy.ts with app-router-entry.ts
- Use const for non-reassigned _waitUntilPromises array
- Add comment explaining waitUntil check ordering in app-router-entry
- Add unit test verifying runMiddleware bubbles up waitUntil promises

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

* fix: move waitUntil settlement before early returns in prod-server

Promise.allSettled(waitUntilPromises) was placed after the !result.continue
check, so redirect/custom-response paths silently dropped promises (e.g.
Clerk session sync on auth redirects). Moved it before the check to match
the pattern in deploy.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(middleware): remove double drainWaitUntil, include waitUntilPromises on error path

* fix: remove dead __vinextWaitUntil read code with no corresponding setter

* fix: remove no-op .catch on Promise.allSettled, strengthen waitUntil redirect test

* test: add waitUntil continue:true path coverage

* fix: settle waitUntil promises in Pages Router dev server (index.ts)

* fix: drain waitUntil promises in generated entry error paths

* regen snaps

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-03-28 22:21:37 +00:00
Stephen Zhou 207588d164 feat: add instrumentation-client support (#679)
* feat: add instrumentation-client support

* trigger ci

* fix deps

* tweaks

* tweaks

* tweaks

* tweaks

* rebuild lockfile

* fix: add app-with-src to CI E2E matrix

---------

Co-authored-by: James <james@eli.cx>
2026-03-28 21:21:36 +00:00
James Anderson 0b69dea1ee refactor: extract OG asset plugins into src/plugins/og-assets.ts (#706)
Move vinext:og-inline-fetch-assets and vinext:og-assets out of the
monolithic index.ts into a focused file (src/plugins/og-assets.ts).

- createOgInlineFetchAssetsPlugin() factory: owns the per-build cache
  (Map<string, string>) and isBuild flag as closure state, removing
  the two module-level variables that leaked into the outer vinext()
  factory closure.
- ogAssetsPlugin constant: the writeBundle-only plugin has no mutable
  state, so it exports directly as a plain plugin object.

index.ts now imports and delegates to these, removing ~190 lines of
inline plugin code from the main plugin file.
2026-03-28 20:57:38 +00:00
James Anderson c291dcd4b7 refactor: extract font plugins into src/plugins/fonts.ts (#705)
Move all Google Fonts and local fonts plugin logic out of the monolithic
index.ts into a single focused file (src/plugins/fonts.ts). The new file
exports createGoogleFontsPlugin() and createLocalFontsPlugin() factory
functions, plus the shared helpers VIRTUAL_GOOGLE_FONTS,
RESOLVED_VIRTUAL_GOOGLE_FONTS, generateGoogleFontsVirtualModule, and
parseStaticObjectLiteral (which index.ts also uses for tsconfig parsing).

index.ts now imports these and delegates to the factories, removing ~340
lines of inline plugin code from the main plugin file.
2026-03-28 20:49:56 +00:00
mehedi c9d1ddbee6 fix(google-fonts): use closure variables instead of this for plugin sstate (#53)
* fix(google-fonts): use closure variables instead of this for plugin state

Vite does not bind `this` to the plugin object when calling hooks like
configResolved and transform.handler. Using `this._isBuild` etc. results
in a TypeError at startup ('Cannot set properties of undefined').

Replace the property-based state with closure variables so the plugin
works correctly regardless of how Vite invokes the hooks.

* fix(google-fonts): fix indentation of plugin object properties inside IIFE return

* fix: update tests to use closure-compatible plugin initialization

Tests were reaching into plugin internals (_isBuild, _fontCache,
_cacheDir) which are now closure variables after the IIFE refactor.
Updated to drive state through configResolved hook and mock fetch
for cache tests.

* fix(google-fonts): apply closure-variable fix on top of main's refactored plugin

main rewrote the plugin to add virtual module import rewriting but kept
the broken this-binding pattern. Apply the PR's fix: wrap in an IIFE so
state is held in closure vars (isBuild, fontCache, cacheDir) instead of
(this as any)._isBuild etc.

* fmt

* fix tests: replace plugin._isBuild direct access with initPlugin() helper

The google-fonts plugin closure fix removes the _isBuild/_fontCache/_cacheDir
properties from the returned Plugin object. Three tests from main's refactor
still used plugin._isBuild = false directly; replace with initPlugin(plugin,
{ command: 'serve' }) to simulate dev mode via the configResolved hook.

* fix: silence no-unused-vars lint warnings in font-google tests

Remove unused cacheDir variable and prefix unused fetch mock parameters
with underscore (_input, _init) per oxlint convention.

---------

Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
Co-authored-by: James <james@eli.cx>
2026-03-28 20:31:02 +00:00
James Anderson 2dacef853f refactor: extract vinext:fix-use-server-closure-collision into its own plugin file (#704)
Move the inline plugin object from index.ts into
packages/vinext/src/plugins/fix-use-server-closure-collision.ts,
following the same pattern as async-hooks-stub, client-reference-dedup,
and optimize-imports. index.ts now imports and references the exported
fixUseServerClosureCollisionPlugin constant. No behaviour change.
2026-03-28 20:30:00 +00:00
dependabot[bot] 9a07314146 chore(deps): bump h3 from 2.0.1-rc.16 to 2.0.1-rc.20 (#703)
Bumps [h3](https://github.com/h3js/h3) from 2.0.1-rc.16 to 2.0.1-rc.20.
- [Release notes](https://github.com/h3js/h3/releases)
- [Changelog](https://github.com/h3js/h3/blob/main/CHANGELOG.md)
- [Commits](https://github.com/h3js/h3/compare/v2.0.1-rc.16...v2.0.1-rc.20)

---
updated-dependencies:
- dependency-name: h3
  dependency-version: 2.0.1-rc.20
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 15:49:53 +00:00
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
Steve Faulkner 05cac9d745 build: align Vite 8 bundler config (#599)
* build: align Vite 8 bundler config

* fmt

---------

Co-authored-by: James <james@eli.cx>
2026-03-28 15:16:03 +00:00
James Anderson 3757b0fbe8 refactor: consolidate fileURLToPath path resolution into resolveEntryPath() (#702)
Replace the repeated `fileURLToPath(new URL(rel, import.meta.url)).replace(/\\/g, "/")`
idiom across entry generators with a single shared utility. Add `resolveEntryPath(rel, base)`
to runtime-entry-module.ts, then use it in app-rsc-entry.ts (16 call sites),
pages-server-entry.ts (8 call sites), and have resolveRuntimeEntryModule delegate to it.
Replace `const __dirname = path.dirname(fileURLToPath(import.meta.url))` in index.ts and
tpr.ts with `import.meta.dirname` (Node 22+, already used throughout the test suite).
2026-03-28 10:39:49 +00:00
Steve Faulkner da04d2062b refactor: extract Pages API route runtime (#683)
* refactor: extract pages api route runtime

* refactor: share Pages ISR cache runtime (#684)

* fix: address Pages API route review feedback

- Replace fragile error.message string matches with typed PagesBodyParseError
  (statusCode: 413) thrown from pages-node-compat.ts and api-handler.ts; the
  existing instanceof catch in handlePagesApiRoute handles both 400 and 413
- Fix res.setHeader('set-cookie') to replace instead of append on repeated
  calls, matching Node.js ServerResponse semantics
- Extract shared getMediaType/isJsonMediaType helpers and the body-parse error
  class into pages-media-type.ts to eliminate drift between api-handler.ts and
  pages-node-compat.ts
- Add unit tests for 404 (null match), missing default export (500), redirect
  (1-arg and 2-arg), writeHead header lowercasing/array joining, setHeader/
  getHeader round-trip, and set-cookie replace-not-append parity

---------

Co-authored-by: James <james@eli.cx>
2026-03-28 09:36:30 +00:00
mounir 892fce6315 fix(app-router-playground): clean warnings and stabilize checks (#694) 2026-03-28 09:20:35 +00:00
Raed Alharbi a515f07a49 test: add static export E2E tests (#564) (#686)
* test: add static export E2E tests (#564)

Add Playwright E2E tests for `output: "export"` static builds covering
both App Router and Pages Router. Includes a lightweight static file
server with path traversal protection, proper error handling, and
startup validation.

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

* Update tests/e2e/static-export/serve-static.mjs

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
2026-03-28 09:06:06 +00:00
JamesbbBriz 73f4ca8d8d feat: implement cacheForRequest() per-request factory cache (#646)
* feat: add cacheForRequest() — per-request factory cache

Implements the cacheForRequest API discussed in #623.

API: import { cacheForRequest } from 'vinext/cache'

- Factory function reference is the cache key (no string collision)
- Async factories cache the Promise; rejected Promises clear their entry
  so subsequent calls within the same request can retry
- Outside request scope: factory runs every time (no caching)
- Public barrel at src/cache.ts, separate from the next/cache shim
- Vite dev alias registered in index.ts
- 6 unit tests covering sync, async, isolation, nested scopes, reject+retry

* fix: address review feedback for cacheForRequest

- Switch test imports from vitest to vite-plus/test (repo standard)
- Use runWithUnifiedStateMutation in nested scope test (real code path)
- Add requestCache assertion to createRequestContext defaults test
- Fix JSDoc @param accuracy for async factory retry behavior
2026-03-28 09:01:03 +00:00
Steve Faulkner b678035956 fix: don't await createFromReadableStream before hydrateRoot (#695) (#700)
* fix: don't await createFromReadableStream before hydrateRoot (#695)

createFromReadableStream returns a React thenable, not a resolved tree.
Awaiting it blocked hydrateRoot until the entire RSC stream was consumed,
which prevented useEffect callbacks from ever firing in production on
Cloudflare Workers where RSC chunks arrive progressively via script tags.

Pass the thenable directly to hydrateRoot so React resolves components
lazily as stream chunks arrive -- matching the RSC contract and the
pattern already used in the SSR entry.

Adds a regression test fixture and e2e test for the Workers hydration
project that verifies useEffect fires after RSC hydration.

* fix: move effect-test page to app-router-cloudflare example

The cloudflare-workers e2e project builds examples/app-router-cloudflare,
not tests/fixtures/cf-app-basic. Move the regression test fixture page to
the correct location so the /effect-test route is actually served.
v0.0.37
2026-03-27 22:07:53 -05:00
Steve Faulkner a62e5c4901 chore(benchmarks): remove Vite 7 runner, update Next.js to 16.2.1 (#699)
* chore(benchmarks): remove Vite 7 runner, update Next.js to 16.2.1

The vinext-rolldown benchmark runner was identical to the vinext runner
(both resolved to the same Vite version via catalog), producing
duplicate results on the dashboard. Remove it entirely and simplify
to a two-runner comparison: Next.js (Turbopack) vs vinext (Vite 8).

Also bump the Next.js benchmark dependency from 16.1.7 to 16.2.1 to
pick up recent performance improvements.

* fix: remove vinext-rolldown from pnpm-workspace.yaml, fix migration comment
2026-03-27 19:23:50 -05:00
Steve Faulkner 5ee1607406 fix: use segment-wise pathname decoding in App->Pages Router fallback (#697)
The App Router to Pages Router fallback path used
decodeURIComponent(url.pathname), which decoded %2F to /, changing
the path structure. This allowed middleware bypass: a request to
/security/admin%2Fpanel would skip the middleware matcher for
/security/admin/:path* but the Pages fallback would decode it to
/security/admin/panel and serve the protected page.

Replace with segment-wise decoding (decodePathParams) that splits by
/, decodes each segment individually, then re-encodes path delimiters
(/, #, ?) via escapePathDelimiters. This preserves the path structure
while still decoding non-ASCII characters within segments.

Both utilities are ported from Next.js:
- escapePathDelimiters: packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
  https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/escape-path-delimiters.ts
- decodePathParams: packages/next/src/server/lib/router-utils/decode-path-params.ts
  https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/decode-path-params.ts

Adds unit tests for escapePathDelimiters (4 tests) and
decodePathParams (8 tests) covering non-ASCII decoding, delimiter
preservation, malformed encoding, and mixed paths.
2026-03-27 10:41:54 -05:00
Steve Faulkner 07b4fb4c70 fix: anchor has/missing condition value regex for full-string matching (#696)
Config rule has/missing conditions used unanchored regex matching,
so a condition like has:[cookie:role=admin] would match cookie values
containing 'admin' as a substring (e.g. 'not-admin', 'admin-temp').

Anchor the regex with ^ and $ to match the full value, matching
Next.js behavior (prepare-destination.ts uses new RegExp(`^${value}$`)).

Adds tests verifying exact match, substring rejection, superstring
rejection, and regex pattern compatibility with anchoring.
v0.0.36
2026-03-27 08:31:08 -05:00
Steve Faulkner 2d03bc569d fix: inline commits into agent prompt to prevent codebase exploration (#693)
The agent kept launching a Task subagent to "explore the vinext codebase
structure" despite instructions not to, consuming the entire 30-minute
timeout. The task permission deny in the agent config was not effective.

Fix: embed the commits JSON directly into the prompt so the agent has
all data immediately and no reason to explore. Also add explicit
"do not explore, do not launch subagents" at the end of the prompt.
2026-03-26 13:54:57 -05:00
Steve Faulkner 1f2cabd9e9 fix: prevent nextjs-tracker agent from wasting time exploring codebase (#692)
The agent was launching a Task subagent to "explore the vinext codebase
structure" which consumed the entire 30-minute timeout. Three fixes:

1. Deny task permission so it cannot launch subagents
2. Add explicit instruction not to explore the codebase
3. Inline the full codebase structure in the agent config so it has
   all the context it needs without file reads
2026-03-26 13:17:45 -05:00
Steve Faulkner 180ba7950a fix: explicitly pass --repo "$GITHUB_REPOSITORY" in nextjs-tracker agent (#691)
The agent hallucinated the repo name as anomalyco/vinext instead of
cloudflare/vinext, causing the gh issue list command to hang for the
entire 30-minute timeout. Explicitly instruct the agent to use the
$GITHUB_REPOSITORY env var for all gh issue commands.
2026-03-26 12:39:44 -05: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 adfc2236cb fix: nextjs-tracker workflow fails because prompt_file is not a valid action input (#687)
The ask-bonk action doesn't support a prompt_file input. The prompt was
silently ignored, causing the scheduled run to fail with 'PROMPT input
is required'. Set the prompt as a multiline step output and reference it
via the prompt input instead.
2026-03-25 08:54:17 -05:00
Steve Faulkner 63cbaad7e0 feat: nextjs-tracker — daily workflow to track Next.js canary changes (#682)
Daily scheduled job that scans Next.js canary commits, uses an AI agent
to classify relevance to vinext, and opens labeled tracking issues for
anything that matters. Supports a dry-run mode for validation.
2026-03-24 21:11:19 -05:00
Steve Faulkner abe0fe5392 refactor: extract Pages page data and ISR runtime (#667) 2026-03-24 20:32:26 -05:00
Steve Faulkner 6cfdaa60a8 fix: percent-encode X-Vinext-Params to allow non-ASCII route params (#677)
Headers.set() requires ByteString values (chars <= U+00FF). When dynamic
route params contain non-ASCII characters (Korean, Japanese, etc.),
JSON.stringify preserves them verbatim which causes a TypeError on the
Headers.set() call in buildAppPageRscResponse.

Fix: encodeURIComponent the JSON on the server side before setting the
header, and decodeURIComponent on the client side when reading it back
during initial hydration and client-side navigation.

Closes #676
v0.0.35
2026-03-24 10:06:06 -05:00
Stephen Zhou fd7618c94a chore: move pnpm settings to pnpm-workspace.yaml (#671) 2026-03-23 09:47:39 +00:00
Nathan Nguyen cc66d4df45 feat: seed memory cache from pre-rendered routes (#645)
* feat: seed memory cache from pre-rendered routes at startup

Read vinext-prerender.json and pre-rendered HTML/RSC files at production
server startup to populate the MemoryCacheHandler. This ensures the first
request to any pre-rendered page is a cache HIT instead of triggering a
full re-render.

- Add seedMemoryCacheFromPrerender() in server/seed-cache.ts
- Add buildId, router, and trailingSlash to the prerender manifest
- Call seeding during App Router production server startup
- 11 unit tests covering ISR routes, static routes, dynamic routes,
  trailingSlash, graceful degradation, and missing files

Closes #561

* fix: address review feedback on cache seeding PR

- Warn on corrupt manifest JSON instead of silent swallow
- Always serialize trailingSlash (typeof boolean, not truthiness)
- Populate setRevalidateDuration() for seeded entries so
  Cache-Control headers are correct on first HIT
- Return seeded route count and log it at startup
- Add tests: corrupt JSON, missing buildId, FNV hash path,
  revalidate duration tracking, return value assertions

Addresses review comments on #645.
2026-03-23 09:43:07 +00:00
Nathan Nguyen 750d8a44e4 fix: use dynamic WASM imports in og-font-patch for Node.js compat (#644)
* fix: use dynamic WASM imports in og-font-patch for Node.js compat

Replace static `import X from "./X.wasm?module"` (workerd-only) with
dynamic `import()` + catch fallback so the same build output runs on
both Cloudflare Workers and Node.js (`vinext start`).

- Yoga WASM (~70KB): dynamic import with inline base64 fallback
- Resvg WASM (~1.3MB): dynamic import with disk-read fallback via
  new URL() + readFileSync + WebAssembly.compile

Fixes #642

* fix: move resvg new URL() inside catch handler for workerd compat

In workerd, import.meta.url is "worker" (not a valid URL base), so
new URL("./resvg.wasm", import.meta.url) at the top level throws
TypeError at module load time. Move it inside the .catch() handler
which only runs on Node.js where import.meta.url is a file:// URL.

* refactor: address review — remove dead atob branch, async readFile, dedup tests

- Remove unreachable atob/Uint8Array fallback in yoga Node.js path
  (mod === null only on Node.js where Buffer always exists)
- Use fs.promises.readFile instead of readFileSync for resvg disk
  fallback (non-blocking, idiomatic in async chain)
- Deduplicate test transforms with shared beforeAll
- Remove unnecessary IIFE in test setup
- Add assertions for Buffer.from and async readFile
2026-03-23 08:59:59 +00:00
James Anderson 8e58ead4b0 fix: defer clearRequestContext() until HTML stream is fully consumed (#660) (#661)
* fix: defer clearRequestContext() until HTML stream is fully consumed

headers()/cookies() throw on warm requests because clearRequestContext()
was called synchronously after receiving the RSC/SSR stream handle, before
any Server Components had actually executed. The RSC/SSR pipeline is lazy —
components run while the response body is pulled by the HTTP layer.

Fix: pipe the HTML stream through a TransformStream whose flush() callback
calls clearRequestContext(), deferring cleanup until the last byte is
consumed. Eagerly-read values (draftCookie, consumeDynamicUsage, etc.) are
still captured synchronously before the stream is returned.

Two sites fixed:
- server/app-page-render.ts (renderAppPageLifecycle, covers both the plain
  and ISR-cacheable HTML paths)
- server/app-page-stream.ts (renderAppPageHtmlResponse)

Regression tests added for both sites.

Fixes #660

* fix: guard against stream-cancelled crashes in tick-buffered transform and early-disconnect cleanup

- createTickBufferedTransform: wrap the setTimeout callback in try/catch so
  that a stream cancellation between scheduling and firing (e.g. a health-check
  that calls res.body.cancel()) does not throw ERR_INVALID_STATE and crash the
  Vite dev server process.
- deferUntilStreamConsumed: add a cancel() handler so clearRequestContext() is
  still called when the HTTP consumer drops the connection before the stream is
  fully drained, preventing per-request context leaks on early disconnect.
2026-03-23 08:41:04 +00:00
Stephen Zhou 7332e4f1a8 feat(optimize-imports): support rename exports (#650) 2026-03-23 08:36:46 +00:00
Stephen Zhou e683e0d84d feat: support inline next config (#649)
* feat: support inline next config

* Apply suggestion from @james-elicx

Co-authored-by: James Anderson <james@eli.cx>

* tweaks

---------

Co-authored-by: James Anderson <james@eli.cx>
2026-03-23 08:25:08 +00:00
James Anderson 364e36bcd8 fix(image): strip priority prop before forwarding to UnpicImage to prevent DOM leak (#662)
* fix(image): strip priority prop before forwarding to UnpicImage to prevent DOM leak

The `priority` prop is a Next.js-specific concept that must never reach the
DOM as an attribute. On the two UnpicImage render paths (remote URL with fill
and remote URL with width+height), `priority={true}` was forwarded directly
to `@unpic/react`'s Image component which did not reliably strip it before
rendering the DOM `<img>`, triggering:

  Received `true` for a non-boolean attribute `priority`.

Fix: replace `priority={priority}` with the equivalent HTML semantics —
`loading={priority ? 'eager' : loading ?? 'lazy'}` and
`fetchPriority={priority ? 'high' : undefined}` — matching what the local
image and custom-loader paths already do correctly.

Adds 10 reproduction tests covering both affected render paths.

* test: add 50ms slack to compressed streaming timing assertion to fix CI flakiness
2026-03-23 07:54:38 +00:00
Steve Faulkner 99ae33f09c refactor: extract pages page response helpers (#657)
* refactor: extract pages page response helpers

* fix: preserve array-valued pages gssp headers
2026-03-22 18:39:15 -05:00
Steve Faulkner 5f1681bce7 Fix tsconfig alias transforms for RSC builds (#655)
* Fix tsconfig alias transforms for RSC builds

* Fix flaky compressed streaming timing test
v0.0.34
2026-03-22 14:07:29 -05:00
Steve Faulkner 7325f0fc7d feat(font): replace generated google font catalog with virtual import rewrites (#656)
Reworks the font-catalog removal from #262 using per-import virtual modules so named imports keep normal ESM semantics, aliased/default calls still self-host in build mode, and dependency imports are transformed too.

Credit to @yunus25jmi1 for the original PR and direction in #262.
2026-03-22 14:06:29 -05:00
MD YUNUS 765a18a565 fix: prevent useActionState state becoming undefined when redirect() is called (#589) (#620)
* fix: prevent useActionState state becoming undefined when redirect() is called

Fixes issue #589 where useActionState receives undefined state when a Server Action
calls redirect() from next/navigation.

Root Cause:
When a server action calls redirect(), the server sends redirect headers with empty body.
RSC navigation expects a valid RSC payload but receives empty body, causing navigation to fail.

Solution:
Use hard redirect (location.assign/replace) for all action redirects. This matches
Next.js's completeHardNavigation behavior and ensures navigation always completes.

Changes:
- packages/vinext/src/server/app-browser-entry.ts: Use location.assign/replace for action redirects
- tests/e2e/app-router/server-actions.spec.ts: Add waitForHydration to test
- tests/fixtures/app-basic/app/actions/actions.ts: Add redirectWithActionState test action
- tests/fixtures/app-basic/app/action-state-redirect/page.tsx: Add test page

Note: This is a known parity gap - Next.js pre-renders the redirect target's RSC payload
for soft navigation, while vinext currently sends empty body requiring hard redirect.

Signed-off-by: Md Yunus <admin@yunuscollege.eu.org>

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* Apply suggestion from @ask-bonk[bot]

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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-22 17:16:05 +00:00
Steve Faulkner 3c5365a5da docs: codify generated entry refactor guidance (#640) 2026-03-21 23:20:18 -05:00
Jared Stowell f709e22209 fix: Pages Router SSR streaming (#514)
* fix pages router streaming

* handle regressions

* fix: align pages response merge behavior

* fix: drop stale worker content-length headers

* fix: preserve no-body rewrite parity

* fix: cancel streamed HEAD responses

* test: support merged prod server shape

* Fix compressed Pages SSR streaming

* Fix ecosystem fixture port collision
2026-03-21 22:44:45 -05:00
Steve Faulkner 114ebac08d refactor: extract app page render lifecycle (#638)
* refactor: extract app page render lifecycle

* test: refresh page render lifecycle generator assertions
2026-03-22 03:37:48 +00: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
Steve Faulkner 76df997bc3 refactor: extract app page html recovery runtime (#636) 2026-03-21 19:24:25 -05:00
Steve Faulkner 42fa020ce7 refactor: extract app page probe runtime (#635)
* refactor: extract app page probe runtime

* fix: handle SSR special errors in app page render
2026-03-21 18:44:03 -05:00
Steve Faulkner 81003c9604 refactor: extract app page boundary helpers (#634) 2026-03-21 15:34:27 -05:00
Steve Faulkner 209675e4cd refactor: extract app page request helpers (#633)
* refactor: extract app page request helpers

* test: fix app router integration assertions
2026-03-21 15:03:07 -05:00
Steve Faulkner 16ac397225 refactor: extract app page stream helpers (#632) 2026-03-21 13:57:28 -05:00
James Anderson 6aa460f9ad fix: HL flight hint regex not matching correct format (#631)
* fix: HL flight hint regex not matching correct format

* update test
2026-03-21 18:39:29 +00:00
Steve Faulkner c0878d7056 refactor: extract app page execution helpers (#630) 2026-03-21 13:32:02 -05:00
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
v0.0.33
2026-03-21 12:19:38 -05:00
Steve Faulkner 8366217c84 refactor: extract app page cache writes (#628) 2026-03-21 11:37:46 -05:00