mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
codex/response-store-do-put-protocol
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bacee9ef52 |
fix(pages): emit canonical __NEXT_DATA__ JSON (#2043)
* fix(pages): emit canonical next data script * test(pages): expect canonical next data markup * fix(pages): initialize canonical next data before routing * test(pages): parse canonical next data markup * fix(pages): preserve router bootstrap ordering * test(pages): expect readiness bootstrap import * fix(pages): guard readiness bootstrap without next data |
||
|
|
0046e0691a |
fix(app-router): preserve encoded prerender params (#1597)
* fix(app-router): preserve encoded prerender params App Router prerendered pages with encoded dynamic params could be bypassed in production and rendered again with decoded route params. This diverged from Next.js for generateStaticParams values like "sticks & stones". The root cause was split across build and startup. The prerender render path only had the encoded URL, so normal route matching decoded the page params. After preserving encoded params for the render, production startup still seeded the ISR cache under the encoded artifact path while live requests lookup cache entries by the runtime-normalized pathname. Carry trusted prerender params through the prerender-only request path, keep decoded params for dynamicParams validation, and seed prerendered App Router artifacts under the normalized runtime cache key while reading files from their encoded output path. * chore(app-router): clean up prerender header internals Reuse the shared record guard for prerender route param parsing and remove the local duplicate. Keep Next.js INTERNAL_HEADERS as the exact upstream list, with vinext-only internal headers tracked separately but stripped by the same request pipeline. This fixes the CI unit failure while preserving the prerender header trust boundary. Factor repeated Node-header conversion and forwarded-protocol handling in the production server to avoid local assertions and duplicate loops. * fix(app-router): scope prerender params to route pattern Bind the internal prerender route-param header to the route pattern that produced it so encoded render params cannot cross a rewrite boundary into a different dynamic route shape. The App Router handler now only applies encoded prerender params when the payload routePattern matches the final matched route. Otherwise it falls back to the decoded params from normal route matching. Tighten the header parser to validate the payload shape with Object.entries/Object.keys and add a regression for a prerendered source route rewritten to a dynamic target route with a different param name. * fix(app-router): verify prerender params match route Require trusted prerender route params to prove both the final route pattern and the final decoded matched params before using their encoded render values. This prevents same-pattern rewrites from carrying stale prerender params into a different concrete route match, while keeping valid prerender renders encoded and static validation decoded. Add handler regression coverage for same-pattern rewrites and helper coverage for catch-all array comparison. * docs(app-router): clarify prerender route-param trust boundary The prerender route-param readers and serializer carried implicit coupling that read as security verification but actually delegated it. A reader new to readTrustedPrerenderRouteParams could assume it verifies the prerender secret, when verification happens once upstream at prod-server's nodeToWebRequest boundary and every downstream read trusts that. The double read-then-reattach in createAppRscHandler also depends on the secret header surviving filterInternalHeaders, with no note of that coupling. Document the trust contract: secret is verified at the outer boundary, downstream reads operate on already-trusted requests gated by VINEXT_PRERENDER=1, and the secret header must stay out of the internal strip list for the second read to succeed. Also document the empty-params to null serialization contract. No behavior change; comments only. |
||
|
|
a66004c71e |
fix: export static App Router metadata files (#1590)
* fix: export static App Router metadata files * fix: address static metadata export review --------- Co-authored-by: James Anderson <james@eli.cx> |
||
|
|
75eb3c1e8d |
feat: production prerender pipeline (#553)
* feat: production prerender pipeline (no dev server)
Add prerenderPages() and prerenderApp() that load from production bundles
exclusively — no ViteDevServer dependency. runPrerender() replaces
runPrerenderWithDevServer() (kept as a shim for cli.ts/deploy.ts callers).
Key changes:
- packages/vinext/src/build/prerender.ts — new: prerenderPages/prerenderApp
- packages/vinext/src/build/run-prerender.ts — new: runPrerender + shim
- packages/vinext/src/build/static-export.ts — delegates to prerender layer
- packages/vinext/src/index.ts — adds rscOutDir/ssrOutDir/clientOutDir/disableAppRouter options
- packages/vinext/src/entries/app-rsc-entry.ts — exports generateStaticParamsMap
- packages/vinext/src/entries/pages-server-entry.ts — exports pageRoutes
- packages/vinext/src/shims/cache.ts — adds NoOpCacheHandler
- packages/vinext/src/server/prod-server.ts — serves prerendered .html files
- packages/vinext/src/build/report.ts — integrates PrerenderResult for build report
- tests/helpers.ts — buildPagesFixture/buildAppFixture helpers (isolated outDirs)
- tests/prerender.test.ts — 36 new tests
- tests/static-export.test.ts/app-router.test.ts/pages-router.test.ts — updated
All 487 tests pass. Typecheck, lint, fmt clean.
* fix: build Pages Router SSR bundle for hybrid app+pages projects
For hybrid projects that have both app/ and pages/ directories, the
App Router multi-env build (createBuilder/buildApp) does not produce
a Pages Router SSR bundle. The prerender phase then fails with 'bundle
not found at dist/server/entry.js'.
Fix: after buildApp() completes, detect pages/ presence and run a
separate standalone Vite SSR build with vinext({ disableAppRouter: true })
so the plugin's multi-env environments config does not override the SSR
input/entryFileNames. The build is run with emptyOutDir: false to preserve
RSC artefacts from the App Router build.
* Clean up run-prerender: remove deprecated shim, fix progress arithmetic and typos
* regen snaps
* refactor: clean up prerender pipeline, cache shim, and report utilities
- Extract loadBundle() helper used by both prerenderPages and prerenderApp
- Save/restore CacheHandler around prerender calls (isolation fix)
- Rename isExplicitlyDynamic -> isConfiguredDynamic for clarity
- Export getRscOutputPath and findDir for cross-module reuse
- Import findDir in run-prerender.ts; remove duplicated inline logic
- Fix single-row table corner character in formatBuildReport
- Remove duplicate JSDoc block before revalidateTag in cache shim
- Add SetCtx interface; eliminate (ctx as any) casts in MemoryCacheHandler
- Replace string sentinel in unstable_cache with structural CacheResultWrapper
- Use completedUrls += 1 consistently in Pages Router phase
* fix: remove unused appDir from PrerenderAppOptions and all callers
prerenderApp() never reads appDir — the bundle is loaded via rscBundlePath
and route scanning is done before the call. Remove the field from
PrerenderAppOptions and AppStaticExportOptions, and drop the now-unused
argument from all callers in run-prerender.ts, static-export.ts, and tests.
* refactor: miscellaneous cleanups across prerender, cache, cli, and deploy
* refactor: document gaps, re-detect project after installDeps, fix Vite resolution in deploy.ts
* refactor(prerender): unify onProgress to single call site; document pageCount semantics
Extract renderUrl() inner function from prerenderApp's concurrency loop so
that onProgress is called exactly once per URL at the outer loop level, rather
than being duplicated inside two early-return branches. Eliminates the risk
of accidentally omitting the callback when adding future early exits.
Add a comment in toStaticExportResult clarifying when pageCount and
files.length can diverge (currently they stay in sync).
* build(prerender): write non-export prerender output to dist/server/prerendered-routes/
On Cloudflare Workers, wrangler.jsonc uses not_found_handling: "none" so
every request hits the worker first. Files in dist/client/ are never
auto-served for page requests — they are uploaded but remain inert.
Writing prerendered HTML/RSC to dist/server/prerendered-routes/ keeps
them co-located with server artifacts and away from the static assets
directory.
This also prevents a future issue: when KV pre-population is implemented,
ISR route files must not be in dist/client/ or they would be served as
stale static files forever (bypassing revalidation).
output: 'export' builds are unaffected — static-export.ts passes its
own outDir explicitly, and runPrerender still writes to dist/client/
when mode === 'export'.
* .
* build: suppress IMPORT_IS_UNDEFINED warnings for generateStaticParams
Dynamic route pages that don't export generateStaticParams produce noisy
IMPORT_IS_UNDEFINED warnings because the virtual RSC entry unconditionally
emits mod?.generateStaticParams for every dynamic route. The optional
chaining guards the access safely at runtime; suppress the build-time noise
in the existing onwarn handler alongside the MODULE_LEVEL_DIRECTIVE filter.
* .
* .
* .
* fix conflict regression
* regen snaps
* update cli comment
* add todo for rsc double req
* encode uri components
* encode uri components
* fix: inherit user plugins in hybrid Pages Router secondary build
The secondary `vinext build` step for hybrid (App Router + Pages Router)
projects was constructed with `configFile: false` and only `vinext({ disableAppRouter: true })`,
dropping all user-supplied plugins including `cloudflare()`. This caused a
false-positive "Missing @cloudflare/vite-plugin" error on any hybrid project
with a `wrangler.jsonc` present.
Fix by inheriting the resolved plugin list from the already-completed App
Router builder, filtering out the `vinext:*` sub-plugins and re-injecting
`vinext({ disableAppRouter: true })` in their place. `configFile: false` is
retained to prevent the user's `environments` block from overriding the SSR
input and `entryFileNames`.
* fix: filter vite:react and vite-tsconfig-paths from inherited hybrid build plugins
vinext auto-registers both @vitejs/plugin-react (vite:react*) and
vite-tsconfig-paths when disableAppRouter is false, so inheriting the
resolved plugin list from the App Router builder caused duplicates.
Filter them out alongside the vinext:* sub-plugins.
* fix: filter rsc:* and vite-rsc-* plugins from inherited hybrid build plugins
@vitejs/plugin-rsc registers resolveId for App Router virtual modules
(virtual:vinext-app-ssr-entry etc.) but the corresponding load hooks live
in vinext:* — which we already strip. With rsc:* present but vinext:* absent
the virtual module resolves but can't load, crashing with PLUGIN_ERROR.
Filter rsc:* and vite-rsc-load-module-dev-proxy alongside the other
auto-registered plugin families.
* fix: skip missing-Cloudflare-plugin guard for hybrid Pages Router secondary build
The secondary build uses configFile:false + vinext({disableAppRouter:true})
so it never loads cloudflare() — by design, it's a plain SSR Rollup bundle
with no Workers config. The configResolved guard was incorrectly treating
this as a misconfigured user build.
Revert the inherited-plugins approach (cloudflare() reconstructs the full
multi-env environments block from its own plugin config hook, independent
of vite.config, causing the App SSR virtual entry to be resolved but not
loadable). Instead, gate the guard on !options.disableAppRouter, which is
exclusively set by this internal invocation.
* fix: inherit user transform plugins in hybrid Pages Router secondary build
The secondary SSR build for hybrid projects (app/ + pages/) was introduced
on this branch and always used configFile:false with only
vinext({disableAppRouter:true}), silently dropping any user transform
plugins from vite.config.ts (SVG loaders, CSS-in-JS, etc.).
Fix by loading the raw user config via loadConfigFromFile (before any
plugin config() hooks fire, so cloudflare() hasn't yet injected its
multi-env environments block) and forwarding all plugins except the
families that vinext auto-registers or that would break the plain SSR
build: vinext:*, vite:react*, rsc:*, vite-tsconfig-paths,
vite-rsc-load-module-dev-proxy, and vite-plugin-cloudflare*.
* fix: reset ANSI styling before prerender and route report output
Vite's logger leaves the terminal in a styled state after build output.
Write an ANSI reset before the prerender label and route report so they
always print at full brightness regardless of preceding Vite output.
* fix: skip instrumentation register() during prerender to prevent process hang
Instrumentation modules like @vercel/otel register OpenTelemetry SDK
exporters with background timers that keep the Node process alive
indefinitely. During prerender these side-effects cause the process to
hang after rendering completes, preventing the route report from printing.
Set VINEXT_PRERENDER=1 in prerenderApp/prerenderPages and gate
__ensureInstrumentation() on that env var so instrumentation is skipped
during prerender builds.
* feat: production prerender pipeline for Cloudflare Workers (App Router + Pages Router)
- Add wrangler devDep to packages/vinext; add `@cloudflare/workers-types` to catalog
- Fix prerender.ts: typed wrangler import, Unstable_DevWorker, undici Response casts,
URL-string extraction for dev.fetch() (no Request object), staticParamsMap Proxy
- Remove process.env.VINEXT_PRERENDER gate from /__vinext/prerender/static-params endpoint
- Add cf-app-basic pages/ fixture (index, about, posts/[slug], api/ping)
- Update buildCloudflareAppFixture to also run buildPagesFixture for Pages Router bundle
- Add CF prerender tests: shared beforeAll, nested App Router + Pages Router describes (46/46)
* .
* feat: CF Workers hybrid build Pages Router prerender support
* regen snaps
* fix(prerender): detect CF Workers build via @cloudflare/vite-plugin in node_modules
Replace wrangler.json file-presence check with node_modules detection,
consistent with how deploy.ts detects CF projects. The old check looked
in dist/server/ which never has a wrangler.json, so isWorkersBuild was
always false for CF projects, causing only the 404 to be prerendered.
The generated dist/server/wrangler.json (from @cloudflare/vite-plugin)
is now used as the config path for unstable_dev, with the project-root
wrangler.jsonc as a fallback. This ensures assets.directory is present,
which wrangler 4+ requires.
* fix(prerender): address bonk review comments
- runWithConcurrency: early return on empty items instead of spawning a
spurious worker via the '|| 1' fallback
- staticParamsMap Proxy: flip has() trap to return false so the typeof-fn
check works for routes without generateStaticParams on CF Workers builds;
also handle null return from the proxy fn (no generateStaticParams) in
the parent-params expansion path
- loadWrangler(): extract shared helper with a two-candidate fallback
(wrangler-dist/cli.js → index.js) used by both prerenderApp and
runPrerender, replacing duplicated path resolution + existence checks
- VINEXT_PRERENDER process.env mutation: add explanatory comments
documenting why the global mutation is intentional and safe for the
sequential-call contract
* refactor(prerender): extract findWranglerConfig, pass CF detection through, cache static-params
- Extract findWranglerConfig(serverDir, projectRoot) helper from both prerender.ts and
run-prerender.ts, eliminating the duplicated 4-candidate wrangler.json search
- Add isWorkersBuild and wranglerConfigPath optional fields to PrerenderAppOptions so
runPrerender can pass its already-computed values into prerenderApp, avoiding a
redundant findInNodeModules walk + 4-candidate fs.existsSync loop on every build
- Add a per-build staticParamsCache (Map keyed on pattern+parentParams) inside the CF
Proxy to dedup repeated /__vinext/prerender/static-params round-trips for deeply
nested dynamic routes
- Add clarifying comment on renderUrl's runWithHeadersContext wrapper explaining it is
a no-op for the CF Workers path (rscHandler is an HTTP proxy; ALS context never
crosses the isolate boundary) but kept for shape-compatibility across both modes
* fix(prerender): address remaining bonk review comments
- Security: gate /__vinext/prerender/* endpoints behind VINEXT_PRERENDER=1 check
to prevent exposure in normal deployments (process.env works for both Node and
CF Workers via Miniflare's var injection into process.env)
- Bug: buildUrlFromParams now throws a clear error when a required param is
missing instead of silently producing 'undefined' in the URL
- Design: add TODO comments for layout-level generateStaticParams limitation in
both resolveParentParams() and generateStaticParamsMap
- Correctness: prerenderPages() now uses runtime module exports (getServerSideProps/
getStaticProps) to classify page type on Node builds instead of static file
analysis; CF builds continue to use classifyPagesRoute() as fallback
* fix(prerender): address latest bonk review comments
- Add shape validation for parentParams on the /__vinext/prerender/static-params
endpoint: JSON.parse result is guarded to ensure user generateStaticParams always
receives a plain object, never a primitive, array, or null
- Add .gitignore for tests/fixtures/cf-app-basic/dist/ to prevent accidental commits
of build output if test cleanup fails (CF fixture builds to source tree, unlike
other fixtures that use tmpdirs)
- Add comment on nextConfigOverride shallow merge in run-prerender.ts to make the
limitation explicit for future maintainers
* regen snaps
* fix: build CF fixture in tmpdir instead of source tree
Use createIsolatedFixture in buildCloudflareAppFixture so the CF Vite
build output goes to a tmpdir rather than tests/fixtures/cf-app-basic/dist/.
Adds an optional nodeModulesDir param to createIsolatedFixture so callers
with fixture-scoped deps (like @cloudflare/vite-plugin) can point the
symlink at the fixture's own node_modules instead of the workspace root.
Removes the stopgap .gitignore and the afterAll that deleted dist/ from
the source tree.
* fix: cache-bust prod-server import() to prevent stale module reuse in tests
startProdServer() used a bare file:// URL for its dynamic import() of the RSC
entry bundle. Node's module cache keyed on that URL, so when two test describe
blocks rebuild to the same output path the second invocation always got the
cached module from the first build. The stale module had __instrumentationInitialized
already set to true and globalThis.__VINEXT_onRequestErrorHandler__ pointing at the
first build's instrumentation instance, whose capturedErrors array lived in a
different module instance than the one the production route handler was reading.
Fix: append ?t=<mtime> to the import URL, matching the pattern used by prerender.ts
loadBundle(). Same mtime means same content (cache hit, no-op); new mtime means
a fresh build and gets a fresh module. Applied to both startAppRouterServer and
startPagesRouterServer.
Also removes debug console.log calls from the instrumentation production test.
* fix: use globalThis for instrumentation test state to survive sequential prod builds
In Vitest, the 'App Router Production build' and 'App Router Production
server' describes run in the same process. The first build's preview server
imports dist/server/index.js uncached, setting
globalThis.__VINEXT_onRequestErrorHandler__ to onRequestError_v1 from the
first module instance. The second build is loaded cache-busted (by mtime),
producing a fresh module instance (v2). After v2 sets the handler, v1's
__ensureInstrumentation can re-fire and overwrite it, causing errors from
v2 to be recorded in capturedErrors_v1 (a different array) while the GET
route reads capturedErrors_v2 (empty).
Fix: store capturedErrors and registerCalled on globalThis (same pattern
as the middleware counter) so all module instances write to and read from
the same shared state regardless of which build instance is active.
Also removes debug logging and restores the afterAll dist cleanup that was
commented out during investigation.
* add process.exit(0) at end of build
|
||
|
|
c17d6941be |
chore: migrate to vite plus (#535)
* chore: migrate to vite plus
* Disable typeAware and typeCheck
* Update CI
* Fix CI
* Fix test
* Clean
* Run test with vp
* Try revert
* react: false In test
* Fix test
* Revert "Try revert"
This reverts commit
|
||
|
|
764a496ce7 |
add oxfmt formatter (#380)
* add oxfmt formatter: config, scripts, CI, editor setup, docs * rebuild lockfile * fix: add Format to required checks list, remove dead ignore pattern * run fmt * add format to agents.md again |
||
|
|
d056e50467 |
fix(config): pass correct Next.js phase to function-form configs + instrumentation test coverage (#273)
* fix(config): pass correct Next.js phase to function-form configs unwrapConfig() hardcoded "phase-development-server" regardless of whether vinext was running in dev or build mode. Apps that branch on the phase argument in next.config.ts received wrong config during production builds. Map Vite's env.command to the correct Next.js phase constant: - "serve" → "phase-development-server" - "build" → "phase-production-build" * test: add instrumentation module test coverage The instrumentation module had zero tests. Add 11 tests covering: - findInstrumentationFile: file discovery across root and src/, priority ordering, null when absent - runInstrumentation: register() lifecycle, onRequestError handler storage, graceful handling of empty modules, error isolation when ssrLoadModule fails - reportRequestError: handler invocation with correct args, no-op without handler, error isolation when handler throws * fix(test): use shared helper for App Router static export test The static-export test had its own startFixtureServer that loaded the fixture's vite.config.ts via configFile. This caused RSC plugin timing issues on Node 25 / macOS — the App Router dev server returned 404 for all pages. The Pages Router section (same approach) worked because it doesn't use the RSC plugin. Switch the App Router section to use the shared startFixtureServerHelper from helpers.ts, which uses configFile: false with explicit plugin registration. This matches how app-router.test.ts (193 passing tests) creates its dev server and avoids the RSC timing issue. * fix: address PR review feedback - Import PHASE_PRODUCTION_BUILD and PHASE_DEVELOPMENT_SERVER constants from shims/constants.ts instead of hardcoding string literals in index.ts and config/next-config.ts - Add afterEach cleanup for temp directories in instrumentation tests * refactor: use phase constants in tests, simplify pure function test setup - Replace hardcoded phase strings in next-config.test.ts with PHASE_PRODUCTION_BUILD and PHASE_DEVELOPMENT_SERVER constants - Remove unnecessary vi.resetModules() from findInstrumentationFile tests — it's a pure function with no module-level state to reset * refactor: use shared startFixtureServer helper in static export tests Remove the local startFixtureServer function — the shared helper from tests/helpers.ts does the same thing. The Pages Router fixture's vite.config.ts is just `vinext()` with no special options, so the shared helper (which uses configFile: false + vinext plugin) works identically. |
||
|
|
12fea722b6 | Initial public release of vinext |