400 Commits

Author SHA1 Message Date
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
Steve Faulkner fc39dbb7d3 refactor: extract app page response helpers (#627)
* refactor: extract app page response helpers

* test: refresh app page response snapshots

* docs: clarify app page response miss policy

* test: document app page response policy edges
2026-03-21 11:31:32 -05:00
Steve Faulkner 32a346a1e3 refactor: extract app page cache helpers (#626)
* refactor: extract app page cache helpers

* chore: tighten app page cache extraction

* test: refresh app page cache snapshots
2026-03-21 11:03:11 -05:00
Gentrit Biba fa03a06bea feat: optimize barrel imports for RSC-incompatible packages (#138)
* feat: add vinext:optimize-imports plugin for barrel import rewriting

Barrel imports like `import { Slot } from "radix-ui"` cause RSC crashes
because Vite eagerly evaluates all re-exported sub-packages, some of which
call React.createContext() — unavailable in the react-server condition.

This plugin rewrites barrel imports into direct sub-module imports on the
server, matching Next.js's optimizePackageImports behavior:

- Parses barrel entry files to build export maps (namespace, named,
  default re-exports, and import-then-export patterns)
- resolveId hook handles pnpm strict hoisting via Vite's own resolver
- Only runs on server environments; client uses Vite's dep optimizer
- Lazy-initialized Set<string> for O(1) package lookups
- Respects experimental.optimizePackageImports from next.config
- Includes Next.js default package list plus radix-ui

Closes cloudflare/vinext#100
Closes cloudflare/vinext#137

* fix: add missing packages to DEFAULT_OPTIMIZE_PACKAGES

Add the full effect ecosystem (17 packages) and all react-icons/*
sub-packages (32 packages) to match Next.js's built-in defaults.

* fix: address bonk review — entryPathCache, buildStart init, Windows path fix, trailing semicolons, fixture-based transform tests

* fix: correct bySource grouping for mixed namespace+named imports, clear barrelCaches on rebuild

- Key bySource map on `${source}::${isNamespace}` instead of just source
  so that a namespace re-export and a named re-export from the same sub-module
  produce two separate import statements rather than one corrupted one.
  Caught by the new 'produces separate statements for namespace and named
  imports from the same source' test.
- Clear barrelCaches.exportMapCache and barrelCaches.subpkgOrigin in buildStart
  alongside entryPathCache so stale barrel analysis doesn't survive rebuilds.
- Add regression test for the mixed namespace+named grouping bug.

* fix: resolve relative barrel re-export paths to absolute, emit default imports for default re-exports

Barrels like lodash-es and lucide-react use relative re-export paths
(e.g. `export { chunk } from './chunk.js'`). Writing those verbatim caused
Vite to resolve './chunk.js' against the importing user file rather than
the barrel package directory.

Fix: resolve any relative source path against the barrel entry's directory
before emitting the replacement import statement, producing an absolute
filesystem path that Vite resolves correctly regardless of where the
importing file lives.

Also fix: `export { default as X }` re-exports now produce `import X from`
(a default import) instead of `import { default as X }` (invalid syntax
that only works via ASI / bundler leniency).

Tests updated to assert absolute paths and added a dedicated test for
default re-export handling.

* refactor: extract barrel optimization to plugins/optimize-imports.ts, add Windows path fix and react-server TODO

- Move all barrel types, helpers, DEFAULT_OPTIMIZE_PACKAGES, and the
  vinext:optimize-imports IIFE from index.ts into a new
  packages/vinext/src/plugins/optimize-imports.ts file with a factory
  function createOptimizeImportsPlugin(getNextConfig, getRoot)
- Forward-slash normalize resolved absolute paths so emitted import
  statements use '/' separators on Windows (path.sep join)
- Add TODO comment in resolveExportsValue noting that the 'react-server'
  condition should be added in a future pass
- Add test for ImportDefaultSpecifier: import MyFoo from 'pkg' rewrites
  to import MyFoo from '<abs>/sub' (22 tests total, all passing)

* fix(optimize-imports): address sixth-pass review comments

- Fix astName() to throw on unexpected nodes instead of String(value)
- Normalize all resolvePackageEntry return values to forward slashes (Windows fix)
- Fix originalName falsy guard: && → !== undefined
- Pre-build quotedPackages in buildStart to avoid per-file template literal allocations
- Add first-wins comment on subpkgOrigin registration
- Add clarifying comment to lucide-react test fixture

* fix(optimize-imports): remove EnvironmentPluginContext cast, expand subpkgOrigin comment

Vite augments rolldown's MinimalPluginContext with `environment: Environment`
via declaration merging, so both resolveId and transform have `this.environment`
typed without any cast. Drop the EnvironmentPluginContext interface and the
`as unknown as EnvironmentPluginContext` cast in the transform handler, using
direct `this.environment` access consistently with resolveId.

Also expand the subpkgOrigin first-wins comment to make explicit why first-wins
is safe: the sub-package specifier resolves to the same path regardless of which
barrel's importer context is used.

* fix(optimize-imports): address seventh-pass review nits

- Fix test imports: vitest → vite-plus/test, Plugin from vite → vite-plus,
  following the convention used by 82 of 85 test files in this repo
- Add TODO comment for extensionless absolute paths produced when barrel
  sources lack file extensions (Vite resolves these correctly in practice)
- Expand subpkgOrigin first-wins comment to explicitly note the nested
  node_modules edge case and why it is out of scope for the default list

* fix(optimize-imports): address james-elicx review comments

- Support wildcard re-exports (export * from './sub') by recursively
  parsing sub-modules and merging their exports into the barrel map
- Add circular-reference guard to prevent infinite loops in recursive
  export map building
- Refactor buildBarrelExportMap into buildExportMapFromFile helper
  with shared recursive logic
- Use switch statements for spec.type handling instead of if-else chains
- Remove non-null assertions and type assertions; use optional chaining
  and typeof guards throughout
- Extract resolvePackageInfo helper to separate concerns in
  resolvePackageEntry
- Tests now import directly from optimize-imports.ts instead of via
  the full vinext() plugin array
- Remove _buildBarrelExportMap re-export from index.ts (no longer needed
  since the plugin has its own file)
- Fix type assertion for optimizePackageImports in next-config.ts using
  a proper type-guard filter
- Apply README nit: 'is always optimized' → 'are always optimized'
- Add tests for wildcard export resolution, no-overwrite semantics,
  and circular re-export safety

* fix(optimize-imports): address ask-bonk review comments

- Resolve relative sources to absolute paths inside buildExportMapFromFile
  so nested wildcard re-exports (e.g. antd-style components/) resolve against
  the correct sub-module directory instead of the barrel root
- Add react-server condition to resolveExportsValue (before node/import) for
  RSC-compatible entry points in packages like react and react-dom
- Add NOTE comment in resolvePackageEntry documenting that only exports["."]
  is checked, not subpath exports
- Eliminate double readFile call in buildBarrelExportMap by passing the
  already-read content to buildExportMapFromFile via new initialContent param
- Update subpkgOrigin guard to check for absolute paths (not startsWith ".")
  since relative sources are now stored as absolute paths in the export map
- Add test for nested subdirectory wildcard path resolution correctness
- Update existing unit tests to expect absolute paths in export map entries

* fix(optimize-imports): only prefer react-server condition in RSC environment

SSR renders with the full React runtime and must not resolve react-server
export condition entries. Thread preferReactServer (env.name === 'rsc')
through resolveExportsValue and resolvePackageEntry, and key entryPathCache
by environment prefix to keep RSC and SSR barrel entries separate.

* fix(optimize-imports): address review nits — TODO comment, type annotation, test names, wildcard dir test

- Add TODO comment on allResolved bail-out suggesting debug logging
- Remove redundant ': string[]' annotation on optimizePackageImports (inferred)
- Rename namespace re-export test to use generic X/barrel names instead of Slot/lucide-react
- Update syntax-error test to match new empty-map-on-parse-error behavior (not null)
- Add test for directory-style 'export * from "./components"' where components/index.js exists

* fix(optimize-imports): address review nits — wildcard extensions, double-read, readFile hoisting, subpkg registration guard

- Add /index.mjs, /index.tsx, and .tsx to wildcard candidate list for ESM-first and TypeScript-first packages
- Pass candidateContent to buildExportMapFromFile in wildcard loop to avoid reading each sub-module file twice
- Add comment clarifying the no-op resolveEntry callback pattern
- Hoist readFileSafe closure outside the import-declaration for-loop (one allocation per transform, not per barrel import)
- Gate subpkgOrigin registration loop with registeredBarrels Set so 50 files importing the same barrel don't each iterate the full export map

* fix(optimize-imports): remove redundant double-cache, extract PluginCtx type alias

- Remove the redundant exportMapCache.set() in buildBarrelExportMap — buildExportMapFromFile
  already stores the result under the same key; the extra set was a no-op but misleading
- Extract PluginCtx type alias for the environment cast so all three hook sites share one
  definition and the verbose inline cast doesn't repeat across resolveId and transform

* fix(optimize-imports): hoist readFileSafe to module level, add namespace import test

- Move readFileSafe from handler scope to module level since it captures
  nothing from the per-file closure; avoids a per-call function allocation
- Add test verifying import * as Pkg from 'barrel' is left unchanged
  (ImportNamespaceSpecifier sets allResolved = false — can't optimize a
  full namespace import to a sub-module)

* fix(optimize-imports): make barrel resolution async, add debug logging, update tests

* fix(optimize-imports): address review feedback — extensions, env-keyed subpkgOrigin, debug gate, graceful astName

* fix: scope registeredBarrels key to env to prevent RSC registration blocking SSR

When RSC and SSR resolve the same barrel entry path (common — most packages
have no react-server export condition), the bare barrelEntry key in
registeredBarrels caused RSC's registration to suppress SSR's inner loop
entirely. SSR's subpkgOrigin map was left empty, making resolveId depend
solely on the cross-env fallback — fragile coupling that could silently break.

Change the registeredBarrels key from barrelEntry to `${envKey}:barrelEntry`
so each environment runs the inner loop and populates its own subpkgOrigin
map independently. Matches the existing entryPathCache key pattern.

Add a regression test covering the shared-barrel-entry scenario.

* fix: handle inline export declarations in barrel sub-modules (date-fns)

date-fns barrels use `export * from "./formatDistanceToNow.js"` where the
sub-module directly declares `export function formatDistanceToNow(...) {}`.
buildExportMapFromFile only handled ExportNamedDeclaration with a source
(re-exports) or specifiers (local re-bindings) — it silently skipped inline
declarations (export function/class/const/let/var), leaving the name absent
from the export map and causing the plugin to log "skipping: could not
resolve specifier" and leave the barrel import unchanged.

Handle the declaration branch: FunctionDeclaration and ClassDeclaration
contribute their id.name; VariableDeclaration iterates all declarators.
The source for inline declaration entries is the sub-module file itself.

Also extend AstBodyNode.declaration typing from `unknown` to the minimal
shape covering all three declaration kinds.

Add three unit tests covering export function, export const (multiple
declarators), and export class; all 38 tests pass.

* refactor: remove DEBUG-gated logging from optimize-imports plugin

* refactor: always emit skip log without DEBUG env gating

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: James <james@eli.cx>
2026-03-21 15:18:54 +00:00
Steve Faulkner f57276a754 refactor: extract app route handler cache helpers (#625)
* refactor: extract app route handler cache helpers

* chore: remove dead app route cache import
2026-03-21 10:18:41 -05:00
Steve Faulkner 4af18c72f5 refactor: extract app route handler execution helpers (#624) 2026-03-21 09:33:26 -05:00
Steve Faulkner 2ae1eaaa1a refactor: extract app route handler policy helpers (#622) 2026-03-21 08:56:41 -05:00
James Anderson 6936601a90 chore(deps): bump nitro for h3 vulnerability (#621) 2026-03-21 09:28:42 +00:00
Steve Faulkner 69259bc002 refactor: extract app route handler response helpers (#619) 2026-03-21 09:07:41 +00:00
Steve Faulkner 30c86e015a refactor: extract app route handler request runtime (#618) 2026-03-20 23:54:36 -05:00
Steve Faulkner a6d2c3a09c Refactor App Router virtual entries into typed runtime modules (#616)
* refactor: move app router SSR/browser runtime into typed modules

* style: format typed app runtime modules

* refactor: address runtime entry review follow-ups
2026-03-20 23:03:27 -05:00
Steve Faulkner 47fec0c871 chore: upgrade vitest to v4 and enable agent reporter (#612)
* chore: bump pnpm to 10.32.1 and enable vitest agent reporter

* chore: upgrade GitHub Actions to Node 24 compatible versions

* perf: re-land unit/integration split and add CI sharding

- Restore unit/integration vitest project split (lost in vp migration)
- vitest 4 supports fileParallelism per-project (was global-only in v3),
  so unit tests now run in parallel (~11s) while integration stays serial
- Add 3-shard matrix for integration tests in CI, cutting wall time from
  ~4.5min to ~1.5min
- Move kv-cache-handler to integration to avoid flakiness under parallelism

* chore: remove unused deploy-preview slash command workflow

* ci: add sentinel job for stable branch protection rule
2026-03-20 16:20:42 -05:00
Steve Faulkner 95905ec7ba fix: serve public directory files after middleware in production (#602)
* fix: serve public directory files after middleware in production

Static file serving in the production server ran before middleware for
all files, including those from the public directory. This meant
middleware could not intercept or redirect requests to public files
like /admin/report.pdf or /internal/data.json.

Split static serving into two phases in both Pages Router and App
Router production servers:
- Before middleware: serve only hashed build assets from /assets/
  (Vite output, always public, no protection needed)
- After middleware: serve all other static files (public directory
  content) so middleware can run first

For Pages Router, middleware response headers (Set-Cookie, security
headers, etc.) are passed through to static file responses via the
extraHeaders parameter.

For App Router, public directory files fall through to the RSC
handler, which runs middleware internally before serving them.

* apply config headers before serving public directory static files

Step 6 (next.config.js headers) now runs before step 5b (public directory
static files), so headers configured via next.config.js headers rules are
included in responses for public directory files, matching Next.js behavior.

---------

Co-authored-by: James <james@eli.cx>
v0.0.32
2026-03-20 10:21:25 +00:00
James Anderson 2ef52ad66b fix: use page extensions for middleware files (#595)
* fix: use page extensions for middleware files

* same thing for instrumentation
2026-03-20 08:45:26 +00:00
Boy Steven 75a03115bb fix: scope getStaticProps revalidate parsing to the exported function (#592)
* fix: scope getStaticProps revalidate parsing

* fix: handle early returns in getStaticProps revalidate parsing

* fix: scope getStaticProps parsing to its declaration

* fix: handle destructured getStaticProps params

* fix: ignore nested helper returns in getStaticProps

* fix: avoid re-export false positives in getStaticProps parsing

* fix: harden getStaticProps revalidate parsing

* chore: polish getStaticProps parser follow-up
2026-03-20 08:36:35 +00:00
Steve Faulkner 3af644f5e9 docs: strengthen Next.js parity verification in agent guidelines (#604)
Add mandatory Next.js behavior verification for all bug fixes and
security work, not just feature development. We repeatedly shipped
fixes that diverged from Next.js because this step was skipped
(config header execution order, javascript: URI blocking, ISR cache
key semantics).

Changes:
- Add local Next.js clone instructions (.nextjs-ref, gitignored)
  for fast ripgrep searches of source and test suite
- Add 4-step verification checklist to Fixing Bugs section
- Require documenting what Next.js does in PRs and issue closures
- Update Looking at Next.js Source with clone instructions
- Add .nextjs-ref/ to .gitignore
2026-03-20 02:04:04 +00:00
Steve Faulkner 9c2fd26990 docs: update Vite 8 guidance (#596) 2026-03-19 20:44:29 -05:00
Steve Faulkner e73a8616e7 fix: validate attribute names in next/head SSR serializer (#597)
The head.ts SSR serializer was interpolating attribute names (the key
variable) directly into HTML without validation. Only attribute values
were escaped via escapeAttr(). This could produce malformed HTML when
attribute names contain characters like quotes, angle brackets, or
slashes.

Add isSafeAttrName() that validates attribute names against a strict
regex (alphanumeric, hyphens, colons, dots) and blocks inline event
handler attributes (on*). Applied in both SSR and client-side
syncClientHead() paths.

Also adds comprehensive unit tests for the validation function.
2026-03-19 20:29:11 -05:00
Steve Faulkner c796d8b771 fix: strip Set-Cookie headers from fetch cache entries (#598)
When storing fetch responses in the cache, all response headers were
included without filtering. This meant Set-Cookie headers from the
original response would be replayed to subsequent requests served from
cache, which is incorrect since Set-Cookie is per-response and should
not persist across different requests.

Both the primary cache write path and the stale-while-revalidate
background refresh path now skip Set-Cookie when collecting response
headers for the cache entry.

Adds a test verifying that the original response retains Set-Cookie
but the cached response does not.
2026-03-19 19:55:24 -05:00
James Anderson 70e7553ae0 fix: throw errors when next config file loading fails (#593)
* fix: throw errors when next config parse fails

* add unit test
2026-03-19 18:24:05 +00:00
James Anderson ea08183af2 fix: respect pageExtensions in prerender and exclude _ prefixed api files (#591)
* fix: respect pageExtensions in prerender and exclude _ prefixed api files

- cli.ts: hoist resolveNextConfig so pageExtensions is passed to printBuildReport
- run-prerender.ts: pass config.pageExtensions to pagesRouter/apiRouter (previously defaulted to ["tsx","ts","jsx","js"] regardless of next.config.js)
- pages-router.ts (scanApiRoutes): add _ prefix exclude, matching pages/ behaviour
- report.ts: tighten printBuildReport signature (pageExtensions required, not optional)
- tests: add coverage for _ prefix exclusion in pages/api and pageExtensions filtering

* PR #591 reviewed: 1 bug found, rest LGTM

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* remove files

* fix: pass pageExtensions to appRouter in prerender; harden afterEach cache cleanup

- run-prerender.ts: appRouter(appDir) was missing config.pageExtensions, same
  class of bug as the pagesRouter/apiRouter fix — App Router prerender would
  scan with default extensions regardless of next.config.js
- build-report.test.ts: afterEach now invalidates both app and pages route caches
  so a test failure can't leak cached routes into subsequent tests

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-03-19 18:10:31 +00:00
James Anderson 7f30707e98 feat(check): flag __dirname/__filename and suggest ESM path APIs (#590)
* feat(check): flag __dirname/__filename and suggest ESM path APIs

Closes #583

Scan server source files for free uses of the CJS globals __dirname and
__filename and report them as a partial compatibility issue in the
vinext check output.

Uses a single-pass alternation regex that skips string literals,
template literals, and comments in one pass, so identifiers inside
those tokens are never matched.

* refactor(check): merge ViewTransition and __dirname scans into one file loop

* Regex misses `__dirname` in template expressions.

Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>

* fix(check): expose __dirname inside template expressions to identifier match

The previous template literal skip branch consumed `${...}` bodies as
literal content, so __dirname in e.g. `${__dirname}/views` was silently
skipped.

Change the template branch to stop at ${ boundaries by excluding $ from
the non-interpolation character class: [^`\\$]. A bare $ not followed
by { is still allowed via |$(?!\{). This means the ${...} body is never
consumed by the skip branch and __dirname inside template expressions is
correctly caught by the identifier branch.

Adds two new tests: one verifying plain template content (no interpolation)
is still ignored, one verifying __dirname inside ${} is detected.

* fix(check): use unsupported status for __dirname/__filename; show files in issues section

There is no vinext shim for __dirname/__filename in user app code — it
is a hard runtime failure, so partial was wrong. Changed to unsupported.

Also fixed formatReport to list affected files under each unsupported
item in the 'Issues to address' section. Previously files were only
shown in the per-section detail view (imports), leaving the summary
section without the context needed to actually fix the issue.

Adds a test verifying the file path appears in the formatted report.

* remove files

---------

Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
2026-03-19 16:23:10 +00:00
James Anderson f78dd2b39f chore: switch back to setup-vp@v1 (#587)
* chore: switch back to setup-vp@v1

* try commit hash

* back to v1
2026-03-19 10:01:45 +00:00
James Anderson 337b65dfb7 feat: support next/dist/* internal imports and auto-generate .js shim aliases (#586)
* feat: add bare next/dist/shared/lib/router-context alias and strengthen test

* feat: add next/dist/shared/lib/router-context to IMPORT_SUPPORT check map

* fix: remove bare router-context alias, only .shared-runtime exists in Next.js

* feat: add all shimmed next/dist/* paths to IMPORT_SUPPORT check map

* refactor: auto-generate .js alias variants via flatMap in nextShimMap

* limit .js map to next/
2026-03-19 09:19:38 +00:00
James Anderson a86b67dff4 feat: full next/compat/router support and fix vinext check scanner for shimmed modules (#584)
* feat: full next/compat/router support and fix check scanner for shimmed modules

- Export NextRouter interface from shims/router.ts so compat-router.ts can use it
- Add NextRouter | null return type to useRouter() in shims/compat-router.ts
- Add next/compat/router, next/form, next/web-vitals, next/constants to IMPORT_SUPPORT in check.ts so vinext check no longer flags them as unsupported
- Add integration fixture pages and components for next/compat/router in both pages-basic and app-basic fixtures
- Add integration tests in tests/pages-router.test.ts and tests/nextjs-compat/hooks.test.ts
- Add unit tests in tests/check.test.ts covering all four newly-registered imports

* test: update entry-templates snapshots for compat-router-test fixture page

* remove type assertion

* fix: add declare module for next/compat/router to fixture next-shims.d.ts files

* fix: add declare module for next/compat/router to internal next-shims.d.ts

* pin to vp setup 1.0.0
2026-03-19 08:47:40 +00:00
Stephen Zhou 746dc5b522 feat!: fumadocs example, mark clientReferenceDedup experimental (#577)
* chore: init fumadocs

* vinext init

* fix dev

* tweaks

* tweaks

* tweaks

* prefer exports

* tweaks

* try new approach

* update

* tweaks

* Update

* exclude deps

* update

* revert

* tweaks

* tweaks

* tweaks

* tweaks

* pre include @unpic/react

* clientReferenceDedup

* tweaks
2026-03-19 07:17:10 +00:00