mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
vinext@0.1.1
55 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bf5b09c46f |
refactor(pages-router): unify triplicated request pipeline into runPagesRequest (#1782) (#1860)
* feat(pages-router): add runPagesRequest pipeline owner with focused tests
Introduces server/pages-request-pipeline.ts which owns the canonical
Next.js 9-step request ordering once. Returns a PagesPipelineResult
discriminated union: type:response for prod/worker callers that supply
render/api callbacks, type:render|api|next intents for dev callers that
omit them. Mirrors createAppRscHandler's thin-closure injection pattern.
Two latent drift bugs reconciled vs the worker copy:
- middlewareStatus now uses result.status ?? result.rewriteStatus
- applyMiddlewareRequestHeaders now passes preserveCredentialHeaders
Adds 28 focused behavior tests covering all pipeline branches.
Adds ./server/pages-request-pipeline export to package.json.
* refactor(pages-router): migrate prod-server.ts to delegate to runPagesRequest
Replaces the ~400-line inlined try block in startPagesRouterServer with a
thin adapter that calls runPagesRequest(webRequest, deps). The adapter
retains only Node-specific concerns: open-redirect guard, decode/400,
prerender endpoint, static assets, image opt, basePath strip, _next/data
normalization, and compression/streaming output.
Extends renderPage callback signature with optional stagedHeaders param
so CSP nonces and other pre-render header injection continue to work.
Copies __vinextStreamedHtmlResponse marker through mergeHeaders to
preserve stream-vs-buffer decision in the adapter.
276 pages-router tests, 112 routing tests, 28 pipeline tests all pass.
* refactor(pages-router): migrate deploy.ts worker template to delegate to runPagesRequest
Collapses the ~280-line inlined fetch handler into ~65 lines. Worker adapter
keeps only: registerConfiguredCacheAdapters, open-redirect guard, static-asset
404, x-nextjs-data header capture, filterInternalHeaders+cloneRequestWithHeaders,
basePath strip with hadBasePath, and image optimization. Delegates the rest to
runPagesRequest via PagesPipelineDeps.
Removes inlined imports: matchRedirect, matchRewrite, preserveRedirectDestinationQuery,
requestContextFromRequest, applyMiddlewareRequestHeaders, isExternalUrl, proxyExternalRequest,
sanitizeDestination, applyConfigHeadersToHeaderRecord, normalizeTrailingSlash, mergeHeaders,
normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute, mergeRewriteQuery.
Updates deploy.test.ts assertions to verify delegation rather than inlined
step logic. 245 deploy tests pass.
* refactor(pages-router): migrate index.ts dev to runPagesRequest, delete parallel helpers
Replaces the inlined dev Pages Router pipeline with runPagesRequest. The
adapter keeps Node-specific concerns: Vite skips, cross-origin guard, image
302 redirect, .html normalization, open-redirect guard, decode/400, basePath
strip with hadBasePath, trailing-slash, _next/data normalization, rawHeaders
snapshot, cloudflare-plugin delegation.
Creates devRunMiddlewareAdapter closure that wraps the dev runMiddleware
(runner, path, i18n, basePath, trailingSlash, isDataRequest args) and
returns a MiddlewareResult-compatible object. Sets VINEXT_MW_CTX_HEADER for
hybrid app+pages mode as a side effect before returning.
Adds writeWebResponseToNodeRes() helper to stream Web Response to Node res
preserving multi-value Set-Cookie. Adds requestHeaders field to render/api
intents so dev adapter can flush post-middleware request headers to req.headers
before calling createSSRHandler/handleApiRoute.
Deletes:
- applyRedirects (lines ~4829-4869)
- proxyExternalRewriteNode (lines ~4867-4928)
- applyRewrites (lines ~4929-4951)
- applyHeaders (lines ~4951-5015)
276 pages-router tests, 112 routing tests, 245 deploy tests, 28 pipeline
tests all pass. vp check clean.
* refactor(pages-router): format deploy.test.ts string literals consistently
* fix(pages-router): restore hasAppDir dev gate; fix external proxy body/headers; update stale after-deploy assertions
Three regressions introduced by the dev pipeline refactor:
1. Hybrid app+pages dev mode: the 'render' intent branch in handlePagesMiddleware
unconditionally called createSSRHandler, missing the original hasAppDir fallthrough
gate. App Router page requests in the hybrid app-basic fixture returned 404 instead
of being deferred to the RSC plugin. Restored by matching the route before calling
the Pages SSR handler and returning next() when there is no pages match and hasAppDir
is true.
2. External proxy body/headers: proxyExternalRewriteNode read the body from the Node
IncomingMessage and the post-middleware headers from req. The refactored pipeline used
a body-less webRequest, so POST bodies were empty and middleware-modified request
headers (x-hello-from-middleware1 etc.) were not forwarded. Fixed by adding an
optional proxyExternal dep to PagesPipelineDeps; the dev adapter supplies it, building
a proper Request from the pipeline's current (post-middleware) headers and the Node
req body stream. Prod and worker adapters fall through to proxyExternalRequest as before.
3. Stale after-deploy assertions: two tests checked old inline call-site strings that
no longer appear in the generated worker entry after the delegation refactor. ctx is
still forwarded (deploy.ts:654,657); updated assertions to match the new call sites.
* fix(pages-router): restore public-file serving + API content-type; gate dev api flushes
Three behavioral regressions from the pipeline extraction, plus the bonk
review findings:
- prod-server (Node): restore post-middleware public-directory static file
serving (original step 5b). The adapter now supplies a serveStaticFile dep
that the pipeline calls after middleware (so middleware can intercept) and
before rewrites. Without it, public/ files (favicon.ico, robots.txt, etc.)
fell through to the renderer and 404'd. Adds a {type:"handled"} result for
callbacks that write their own output.
- prod-server (Node): restore the application/octet-stream content-type default
for API responses. The unified response path collapsed API and page fallbacks
to text/html; the pipeline now tags API results with isApiResponse so the Node
adapter picks octet-stream (arbitrary data) over text/html (content-sniffing
hazard). [bonk finding 1]
- index.ts (dev): gate the staged-header/req-header flushes behind a pages-api
match, mirroring the original 'if (apiMatch)' guard. The unconditional
flushRequestHeaders() wiped req.headers (incl. VINEXT_MW_CTX_HEADER) on the
api-miss -> next() path, dropping middleware context for app/api/* routes in
hybrid app+pages dev mode.
- pipeline: note {type:"next"} is reserved/unused [bonk finding 2]; document
the intentional external-proxy merge asymmetry on the bare proxyExternal
returns [bonk finding 3].
Adds 5 pipeline tests (serveStaticFile/handled ordering, isApiResponse tag).
* fix(pages-router): forward staged headers to worker renderPage; gate data-request defer
Two divergences found in code review of the extraction:
- deploy.ts (worker): the renderPage adapter dropped the pipeline's 4th
stagedHeaders arg, passing undefined for the SSR renderer's middlewareHeaders
param — so middleware-set CSP nonces were never applied to rendered HTML on
the worker, unlike the prod path. Widen the wrapper to forward stagedHeaders.
- pipeline: shouldDeferErrorPageOnMiss only gated on isDataReq, which the worker
never sets (it doesn't normalize /_next/data paths). The pre-refactor worker
gated on the x-nextjs-data header (isDataRequest), so a data-request miss now
wrongly deferred + ran fallback rewrites + re-rendered. Gate on both signals;
no-op for Node/dev where a data request already has isDataReq=true.
Updates the after-deploy assertion to the new call site and adds a pipeline
test for the data-request defer gate.
* fix(pages-router): address code-review findings (content-type, redirect query, dedup)
- prod content-type: passthrough responses (middleware short-circuits, external
proxies) no longer get a text/html default injected. Generalize the response
tag to defaultContentType (render -> text/html, api -> octet-stream, passthrough
-> unset); the Node adapter sends untagged responses verbatim via sendWebResponse,
matching pre-refactor behavior.
- redirect query: config-redirect Location now uses the raw req.url query (via a
new rawSearch dep supplied by the Node prod + dev adapters) instead of
new URL().search, which re-encoded chars and truncated at a literal '#'. The
worker keeps url.search (it only ever had a Web Request). Restores the original
prod/dev behavior.
- efficiency: matchPageRoute no longer runs twice per request on the common
no-afterFiles-rewrite path; reuse the Step 12 match unless afterFiles changed
the pathname.
- dedup: collapse prod-server's mergeWebResponse into the canonical mergeHeaders
(worker-utils) via delegation, removing the duplicate implementation and the
now-orphaned hasHeader/stripHeaders helpers. Existing mergeWebResponse tests
validate the delegation.
Updates pipeline tests for defaultContentType and adds a passthrough-verbatim
assertion. 36 pipeline + 309 features + 276 pages-router + 245 deploy + 9
after-deploy + 112 routing pass; vp check clean.
---------
Co-authored-by: James <james@eli.cx>
|
||
|
|
a74f833a10 |
feat(deploy): honor Worker-entry cache setters for ISR deploys (#1821)
Older apps that wired a cache backend imperatively in their Worker entry (setCacheHandler / setDataCacheHandler / setCdnCacheAdapter) were blocked on deploy because the ISR cache-adapter check only inspected the Vite config. Add workerEntryHasCacheHandler() so those backwards-compatible entries are still considered configured. Also drop the cdn adapter suggestion from formatMissingCacheAdapterError; the message now points only to the kvDataAdapter() data cache. |
||
|
|
9bab92f012 |
test(deploy): cover generated worker package exports (#1807)
Generated worker entries can import vinext subpaths that must resolve from the published package. Missing package exports break consumers even when the generated template itself looks correct. Add deploy coverage that extracts vinext subpath imports from generated App and Pages worker entries and verifies each one is covered by packages/vinext/package.json exports. Tighten the prefer-shared-utils lint rule so copied shared helpers in repo tests are reported too. Test files mask template literal fixture payloads, while source files still scan generated-source templates. |
||
|
|
b324cbe04c |
refactor(utils): dedupe shared helpers and lint redefinitions (#1793)
* refactor(utils): dedupe shared helpers and lint redefinitions Shared helper logic was being reimplemented across generated entries, runtime modules, and shims. That made small utility semantics easy to drift and gave agents no mechanical feedback when they hand-rolled copies. Add a local Oxlint rule that discovers exported shared helpers and reports local redefinitions, including helpers embedded in generated source strings. Centralize the existing low-risk duplicates through shared utility modules. * fix(lint): harden shared utility rule validation * test(lint): avoid matching oxlint success summary * fix(lint): make shared utility rule cwd independent * fix(lint): address bonk review comments - Drop redundant "default" export conditions in package.json; the generated worker template resolves these subpaths via "import" like its siblings (request-pipeline, pages-i18n, config-matchers). - Mask backtick template literals in prefer-shared-utils so an apostrophe or comment sequence inside generated template source can no longer open string/comment masking and swallow a subsequent helper definition (false negative). Template bodies stay scannable. --------- Co-authored-by: James <james@eli.cx> |
||
|
|
db353fca92 |
fix(deploy): respect --env flag when invoking build (#1694)
* fix(deploy): respect --env flag when invoking build - Add withCloudflareEnv helper to set CLOUDFLARE_ENV around builder.buildApp - Thread --env through runBuild in deploy command - Add --env parsing to parseArgs so vinext build --env also works - Document --env in vinext build --help - 12 new unit tests across deploy and cli-args Fixes #1210. * fix(deploy): scope --env fix to deploy command only Drop the --env addition for vinext build (cli-args.ts, cli.ts, and the cli-args build tests). The deploy-side fix in deploy.ts remains. vinext build should remain platform-neutral; Cloudflare-specific concerns belong in the deploy path. |
||
|
|
8d00797ada |
feat(cache): extract Cloudflare cache adapters into @vinext/cloudflare (#1748)
* refactor(cache): extract Cloudflare cache adapters into @vinext/cloudflare Move the Cloudflare KV data cache and edge CDN cache adapters out of vinext into a new publishable @vinext/cloudflare package: - cache/kv-data-adapter(.runtime).ts (KVCacheHandler, kvDataAdapter) - cache/cdn-adapter(.runtime).ts (CloudflareCdnCacheAdapter, cdnAdapter) tpr.ts stays in vinext. vinext now depends on @vinext/cloudflare (workspace:*) and the package declares vinext as a peer dep; both build from source via tsconfig paths so there is no build-order cycle. The vinext/cloudflare barrel still re-exports KVCacheHandler for back-compat. Wires up tsconfig paths, a vitest source alias, root build/postinstall, and the preview/publish workflows for the new package. Updates internal consumers (apps/web, examples/workers-cache), docs, and tests. * ci(create-next-app): install @vinext/cloudflare from local tarball vinext now depends on @vinext/cloudflare, which isn't published to npm yet. The create-next-app smoke test packs vinext locally and resolves its deps from the registry, so the install (and dev server) failed with ERR_PNPM_FETCH_404 for @vinext/cloudflare. Pack @vinext/cloudflare alongside vinext and add a pnpm override in the scaffolded project pointing at the local tarball so the dependency resolves offline. * refactor(cloudflare): address review feedback - Remove the root barrel export from @vinext/cloudflare; expose only the ./cache/* subpaths via a wildcard export (no root main/types). - vinext/cloudflare re-exports KVCacheHandler from the full subpath. - Drop the redundant .npmignore (the package.json "files" allowlist already restricts the publish to dist). - Remove the unsupported imperative setCacheHandler/KVCacheHandler usage from both READMEs; the cache plugin config is the supported approach. - Simplify test wiring: drop the now-unused @vinext/cloudflare tsconfig path and dedupe the vitest source alias into a shared constant. * chore(cloudflare): drop unused vite devDependency The @vinext/cloudflare config uses vite-plus and nothing imports vite, so the vite devDependency was unused. build/check/knip stay green without it. * Apply suggestion from @james-elicx |
||
|
|
f0f6aa72e0 |
feat(cache): configure cache adapters from vite plugin config (#1733)
* feat(cache): configure cache adapters from vite plugin config
Add a `cache` option to the vinext() plugin so CDN and data cache
adapters can be declared in vite.config instead of calling
setDataCacheHandler() / setCdnCacheAdapter() from a worker entry:
vinext({
cache: {
cdn: { adapter: require.resolve('vinext/cloudflare/cache/cdn-adapter') },
data: { adapter: require.resolve('vinext/cloudflare/cache/kv-data-adapter') },
},
})
Each slot points at an adapter module whose default export is a factory
(DataCacheAdapterFactory / CdnCacheAdapterFactory). The plugin generates
a virtual:vinext-cache-adapters module that the App Router worker entry
calls per request (self-guarded, once per isolate), passing the host env
so binding-backed adapters (e.g. KV) can read their namespace.
Ships ready-made Cloudflare adapter entry points:
- vinext/cloudflare/cache/kv-data-adapter (KVCacheHandler)
- vinext/cloudflare/cache/cdn-adapter (CloudflareCdnCacheAdapter)
* feat(cache): add typed adapter builders (kvDataAdapter/cdnAdapter)
Instead of `{ adapter: require.resolve(...) }`, each adapter module now
also exports a config-time builder from the same path:
import { cdnAdapter } from 'vinext/cloudflare/cache/cdn-adapter';
import { kvDataAdapter } from 'vinext/cloudflare/cache/kv-data-adapter';
vinext({ cache: { cdn: cdnAdapter(), data: kvDataAdapter({ binding: 'MY_KV' }) } })
A builder returns a plain, serializable { adapter, options } descriptor —
it never touches the Workers runtime, so nothing throws at config / build
/ dev time when bindings aren't available. Descriptor `options` (e.g. the
KV binding name) are inlined into the generated registration module and
forwarded to the factory's { env, options } context, where the binding is
resolved lazily on the first request.
- shims/cache-adapter: descriptors + options-aware factory/context types
- kv-data-adapter: kvDataAdapter() builder + configurable binding/appPrefix/ttl
- cdn-adapter: cdnAdapter() builder
- raw { adapter, options } path form still supported
* test(cache): verify absolute (require.resolve) local adapter path bundles
Real Cloudflare build pointing cache.data at a local adapter file by
absolute path (what require.resolve('./adapter') yields). Proves the
generated registration module resolves the absolute import, bundles the
local adapter into the worker, and does not need any Workers context at
build time.
* refactor(cache): builder require.resolve + register across all routers/runtimes
Addresses review feedback:
* Move adapters into their own runtime modules instead of re-exporting.
Each adapter is now a builder module (kv-data-adapter.ts / cdn-adapter.ts)
plus a sibling *.runtime.ts holding the default-export factory. Type
definitions have a single home in shims/cache-adapter.ts (dropped the
re-export shim; index.ts imports the config type from there).
* The exposed builder utility resolves the relative runtime path internally
via import.meta.resolve (the ESM require.resolve), so the descriptor carries
an absolute path to the runtime factory rather than a bare specifier — the
example is just kvDataAdapter({ binding }), no require.resolve at the call site.
* Register configured cache handlers EVERYWHERE, not just the App Router worker:
- App Router: the generated RSC entry passes registerConfiguredCacheAdapters
into createAppRscHandler, which calls it per request — covering Workers,
the Node server, and dev through the one shared handler.
- Pages Router: the generated server entry registers in renderPage and
handleApiRoute (Node/dev), and the generated worker registers with env
(Workers, for KV bindings).
Registration self-guards (first call with real env wins) and is now resilient:
a factory that throws on an incompatible runtime is logged and skipped, so the
default handler stays in place instead of failing every request.
Tests: generator-level assertions that every router/runtime entry wires
registration, plus the existing builder/codegen/factory and full-build coverage.
vp check clean; app-router (339) and pages-router (272) suites pass.
* refactor(cache): keep all Cloudflare adapter code under cloudflare/
The adapter factory contract lived in shims/cache-adapter.ts (outside
cloudflare/), and the Cloudflare adapters reached out to it. Move the
contract into cloudflare/cache/adapter.ts so every Cloudflare-specific
cache adapter file is self-contained under cloudflare/ — importing only
cloudflare-local modules and the core CacheHandler/CdnCacheAdapter
interfaces it implements.
The plugin's config schema (CacheAdapterDescriptor / VinextCacheConfig)
is genuinely framework-level (it's the vinext() `cache` option), so it
moves into the codegen module the plugin already owns; index.ts imports
it from there. Builders return a structural { adapter, options } so they
don't import the descriptor type either. Deletes shims/cache-adapter.ts.
* refactor(cache): merge KV/CDN classes into the runtime adapter files
All Cloudflare cache code now lives in one directory, cloudflare/cache/,
and each runtime file holds both the implementation class and its
config-driven factory (no separate class module to reach for):
- kv-cache-handler.ts -> cache/kv-data-adapter.runtime.ts
(KVCacheHandler + ENTRY_PREFIX + createKvDataCacheAdapter default export)
- cloudflare-cdn-cache.ts -> cache/cdn-adapter.runtime.ts
(CloudflareCdnCacheAdapter + createCloudflareCdnCacheAdapter default export)
Updated importers: cloudflare/index.ts re-exports the classes from the
runtime files, tpr.ts pulls ENTRY_PREFIX from there, shims/cdn-cache.ts
imports the edge adapter from there, and the tests follow the moved paths.
git mv preserves history.
vp check clean; cache/kv/cdn/app-route/tpr/shims suites pass (1300+ tests).
* chore(cache): trim low-value comments added in this branch
Remove narrating/redundant comments that just restated the code; keep
the non-obvious why (registration ordering/resilience, import.meta.resolve
rationale, edge cache-control semantics). No code changes.
* review: address PR #1733 feedback
- Make registerCacheAdapters a required field on the RSC handler options
(the generated entry already passes it; test factory updated).
- Remove the separate cloudflare/cache/adapter.ts contract file; inline the
factory param types directly into the two runtime adapters.
- Drop the CloudflareCdnCacheAdapter re-export from cloudflare/index.ts.
- Fold the virtual:vinext-cache-adapters declaration into global.d.ts and
delete the standalone .d.ts.
- Remove the ./cloudflare/cache/* package.json export for now; README uses a
local-adapter require.resolve example with a note that the built-in adapter
export paths are pending.
- Rename the config-driven KV default binding to VINEXT_KV_CACHE (imperative
deploy/tpr path keeps VINEXT_CACHE — flagged on the thread).
* refactor(cache): align KV binding name to VINEXT_KV_CACHE everywhere
Rename the KV cache binding from VINEXT_CACHE to VINEXT_KV_CACHE across the
whole codebase so the config-driven adapter, the imperative deploy-generated
worker, TPR's wrangler detection, and the apps/web example all agree. The
unrelated X-Vinext-Cache response-header constant (VINEXT_CACHE_HEADER) is
untouched.
* tidy
* .
* .
* .
* .
* .
* Move apps/web cache to plugin config
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
---------
Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: james-elicx <james-elicx@users.noreply.github.com>
|
||
|
|
48e932e615 |
feat(cache): split CDN and data cache adapters; add Cloudflare edge adapter (#1693)
* feat(cache): split CDN and data cache adapters; add Cloudflare edge adapter Separate two caching concerns behind distinct adapters: - Data cache handler (existing CacheHandler): fetch, "use cache", unstable_cache. Canonical get/setDataCacheHandler; get/setCacheHandler kept as deprecated aliases. - CDN cache adapter (new): page-level ISR serving strategy — readPage/writePage, buildResponseHeaders (header map), ownsBackgroundRevalidation, revalidate. DefaultCdnCacheAdapter delegates storage to the data cache, so default behavior is unchanged. Page/route ISR now routes through the CDN adapter (isr-cache, app-page-cache); revalidateTag/revalidatePath/updateTag invalidate the data cache and purge the CDN adapter (default no-op). Add CloudflareCdnCacheAdapter (edge-managed): readPage null / writePage no-op, ownsBackgroundRevalidation false, emits CDN-Cache-Control for SWR + Cache-Control: no-store (no browser storage) + Cache-Tag, and purges via the request-context cache. Auto-selected via a global detector when the request context exposes a cache handle; explicit setCdnCacheAdapter wins. The request-context type stays generic (cache: unknown). Adds next.config cdnCacheHandler (symmetric with cacheHandler) and updates the worker codegen to setDataCacheHandler. * address review: drop config plumbing; refine Cloudflare cache headers - Remove the cdnCacheHandler next.config plumbing entirely (deferred); next-config.ts is back to baseline. - CloudflareCdnCacheAdapter: emit the edge directive on CDN-Cache-Control as 'public, max-age=…, stale-while-revalidate=…' (max-age, not s-maxage, so the edge caches + SWRs), and set the browser-facing Cache-Control to 'public, max-age=0, must-revalidate' so a browser never serves a stored copy without revalidating against the edge. * chore: fix formatting (vp check) for cache adapter files * feat(cache): route App Router route handlers + Pages Router ISR through the CDN adapter Closes the two parity gaps from review: route-handler and Pages Router ISR responses now emit the CDN adapter's headers (CDN-Cache-Control + Cache-Tag on edge adapters) instead of a hardcoded Cache-Control. - Add shared applyCdnResponseHeaders() in cache-control.ts; app-page-cache now uses it (drops its local helper). - Route handlers: applyRouteHandlerRevalidateHeader (fresh) and buildRouteHandlerCachedResponse (HIT/STALE) go through the adapter; routeTags hoisted in execution so the fresh response carries Cache-Tag. - Pages Router: fresh ISR response emits a path-based Cache-Tag (matching revalidatePath); HIT/STALE served response routes through the adapter. - CloudflareCdnCacheAdapter: guard so non-cacheable policies (no-store/no-cache/private) are never promoted to CDN-Cache-Control. Default behavior unchanged (adapter yields a single identical Cache-Control). * address review: simplify applyCdnResponseHeaders + strip CDN headers from stored route values - applyCdnResponseHeaders now clears only Cache-Control (the header vinext stamps internally); the adapter's own headers are applied via set() which overrides, so pre-clearing adapter-specific headers was redundant and presumptuous (per review). - buildAppRouteCacheValue strips cdn-cache-control / cache-tag so CDN policy headers are never baked into a stored route value (re-derived from the adapter on every served response). - pages-page-data buildPagesCacheResponse now uses applyCdnResponseHeaders (Headers) for consistency with every other call site. * revert presumptuous CDN header strip in buildAppRouteCacheValue Hardcoding cdn-cache-control/cache-tag in the store denylist presumes a specific adapter's header names (the same presumption rejected for applyCdnResponseHeaders) and is also unnecessary: CDN policy headers never reach a stored route value — the edge adapter's writePage is a no-op and the default adapter never emits them. Back to the original denylist. * example(workers-cache): add demo app for route-cache testing (#1695) * example(workers-cache): add demo app for route-cache testing Adds the examples/workers-cache-cloudflare demo app from feat/route-cache-request-context so the route-cache CDN adapter changes on this branch can be tested. * ci: trigger PR workflows on any base branch, not just main Drop the `branches: [main]` filter from the pull_request trigger in ci.yml, deploy-examples.yml, and preview-release.yml so these workflows run on PRs against any base branch (e.g. stacked PRs). * update lockfile * ci(deploy-examples): add workers-cache-cloudflare to deploy matrix Pull in the deploy matrix + preview-URL comment entry from feat/route-cache-request-context so the workers-cache demo app gets built, deployed, and linked on PR previews. * fix(cache): auto-select edge CDN adapter from request context, no import needed The edge-managed CDN cache adapter was only activated when a detector got registered as a side effect of importing `vinext/cloudflare`. Apps with a hand-written worker entry (e.g. the workers-cache demo) never imported it, so ISR silently fell back to the origin-managed default — emitting plain `Cache-Control` instead of `CDN-Cache-Control` / `Cache-Tag`. The adapter is platform-agnostic (it only touches the generic request-context cache surface), so move it into core as `RequestContextCdnCacheAdapter` and have `getCdnCacheAdapter()` select it directly. Resolution is now: 1. explicit `setCdnCacheAdapter()` (always wins) 2. request-context cache present (`ctx.cache`) -> edge adapter 3. otherwise -> origin-managed default Drops the detector-registry indirection and the import/registration requirement. `CloudflareCdnCacheAdapter` is kept as a re-export alias for backwards compatibility. * fix(cache): select Cloudflare edge adapter from resolver, keep it in the cloudflare module Previous commit moved the adapter into core — revert that. The CloudflareCdnCacheAdapter stays in cloudflare/cloudflare-cdn-cache.ts; the core resolver imports it and instantiates it as the built-in default when the request context exposes a host cache (ctx.cache). Drops the detector-registry side-effect-import requirement; resolution is explicit -> ctx.cache edge adapter -> origin-managed default. * example(workers-cache): show CDN-Cache-Control in the probe headers * example(workers-cache): rename example app from workers-cache-cloudflare to workers-cache Rename the example directory and update its package name, wrangler worker name, the deploy-examples matrix + preview-URL list, and the lockfile. * fix(cache): give bare stale-while-revalidate an explicit window for the CF edge The framework emits a value-less `stale-while-revalidate` (Vercel's unbounded extension). Cloudflare follows RFC 5861 and ignores the bare directive, so the edge had no stale window — entries hard-expired at max-age and the next request was a MISS instead of UPDATING. Normalize bare SWR to an explicit 1-year window in the edge adapter's toEdgeCacheControl so Cloudflare actually serves stale while revalidating. * use link component * fix(cache): let the CDN adapter own the default Cache-Control when none is set Rendered responses that produced no cacheable policy (e.g. dynamic App Router pages) were going out with no Cache-Control at all, bypassing the CDN adapter — so on the edge Cloudflare applied its own default caching heuristic instead of the adapter's policy. Add a guard in finalizeAppRscResponse (the single App Router egress, already run for every page/route-handler/metadata/not-found response) that, when no Cache-Control is present, routes through the adapter to supply the default: the edge adapter emits no-store (never accidentally edge-cache an unspecified response), the default adapter leaves it absent (unchanged). Runs only when the header is absent, so it never clobbers a policy a renderer already applied (incl. CDN-Cache-Control). Also stop applyCdnResponseHeaders from stamping an empty Cache-Control value. * refactor(cdn-cache): align adapter method names with data cache + gate ctx.cache auto-detection Address PR #1693 review feedback: - Align CdnCacheAdapter field naming with the data cache adapter (CacheHandler): readPage->get, writePage->set, revalidate->revalidateTag. buildResponseHeaders / ownsBackgroundRevalidation stay CDN-specific (no CacheHandler equivalent). Updated both implementations and all call sites. - Gate ctx.cache auto-detection behind VINEXT_CDN_CACHE_AUTO_DETECT (default off) so edge-managed page ISR is opt-in until deployment skew protection is figured out. Removed the dedicated _edgeAdapter variable; the resolved edge adapter is now stored on the single active-adapter global slot that setCdnCacheAdapter uses. Enable the flag for the workers-cache demo via wrangler.jsonc vars. * test(cdn-cache): update tests for renamed adapter API + flag-gated auto-detect Align the CDN adapter unit tests with the refactor: - get/set/revalidateTag method names (was readPage/writePage/revalidate) - bare stale-while-revalidate now normalized to an explicit window - auto-detection is gated behind VINEXT_CDN_CACHE_AUTO_DETECT (no detector / no vinext/cloudflare side-effect import) * chore: reconcile pnpm-lock.yaml after merge The merge auto-resolved pnpm-lock.yaml into a broken state (missing @vitejs/plugin-rsc entry), so `vp install` failed at CI setup. Regenerated with pnpm install --no-frozen-lockfile; frozen install now passes. |
||
|
|
fc317081fd |
fix(deploy): plain-text 404 for invalid _next/static in Pages Router worker (#1630)
The Pages Router Cloudflare worker template generated by `vinext deploy` forwarded asset-shaped misses to `renderPage`, which renders the full HTML 404 page (with bootstrap scripts + CSS) instead of the plain-text "Not Found" body Next.js emits. The App Router worker entry and prod server already short-circuit these requests; only the Pages Router worker template was missing the check. Adds `isNextStaticPath` + `notFoundStaticAssetResponse` to `generatePagesRouterWorkerEntry()` so invalid `_next/static/*` requests (including those under `basePath` or `assetPrefix`) return `text/plain; charset=utf-8` "Not Found" before the renderer runs. Matches Next.js: packages/next/src/server/lib/router-server.ts. Fixes #1337 |
||
|
|
5b633b0284 |
fix(pages): render masked basePath error routes (#1441)
* fix(pages): render masked basePath error routes
Pages Router route misses under basePath returned generic HTML and masked client navigations to /404 or /_error could fetch the visible as-path instead of the error route. This diverged from Next.js when applications used next/router to preserve a friendly URL while rendering the error component.
The implementation assumed the browser URL and the component route could always collapse to one fetch target. Preserve the masked history URL, fetch the Pages error route as the HTML target, allow its 404 response to hydrate, and render pages/404 for production route misses with the correct status and client module metadata.
* test(pages): keep error navigation coverage out of vitest browser
Vitest integration CI does not install Playwright browsers, so launching Chromium from tests/pages-router.test.ts made the PR fail deterministically even though the upstream deploy harness passed.
Keep the browser-level verification in the upstream deploy-suite run and cover vinext locally at the lower boundaries: production 404 rendering through HTTP and Pages Router masked error-route navigation through the router shim. The generated Pages entry now also shares one SSR manifest module lookup helper between asset tag collection and client module URL resolution.
* fix(prerender): keep custom 404 out of generic page loop
* fix(pages): fall back to _error on route misses
* fix(pages): document the catch-all guard on matchRoute("/404")
The pattern check prevents a dynamic catch-all route (e.g. [...slug]) from
being used as the 404 fallback. Without it, matchRoute can return a
catch-all match and silently hijack the error rendering path.
* fix(pages): preserve error navigation rewrite order
* fix(pages): limit fallback rewrites to route misses
* fix(deploy): skip deferred error rendering for data requests
* fix(pages): preserve ISR response status
* fix(pages): keep module metadata in ISR regeneration
|
||
|
|
61cf2e78f5 |
fix(image): emit /_next/image URLs (#1562)
* fix(image): emit /_next/image URLs to match Next.js Closes #1513 The default image loader and optimization endpoint switched from the vinext-specific /_vinext/image path to Next.js's canonical /_next/image. This unblocks the deploy suite tests that import Next.js's expected URL shape (/_next/image?url=...&w=...&q=...). * refactor(image): use IMAGE_OPTIMIZATION_PATH constant at remaining call sites Replace hardcoded "/_next/image" strings in index.ts, app-rsc-handler.ts, and the generated worker entry templates in deploy.ts with the IMAGE_OPTIMIZATION_PATH constant from server/image-optimization, matching the pattern already used in prod-server.ts. Prevents future drift if the path ever changes again. * feat(image): accept both /_next/image and /_vinext/image at the optimizer Add a VINEXT_IMAGE_OPTIMIZATION_PATH constant and an isImageOptimizationPath() helper, then route through every match site (prod-server, dev server passthrough, app RSC handler, generated worker templates, and shipped example workers). Apps that wire image URLs to either prefix now hit the same handler; new URLs are still emitted via IMAGE_OPTIMIZATION_PATH. |
||
|
|
46ab691094 |
fix(i18n): normalise default-locale paths before route matching (#1409)
Mirrors Next.js's server-side default-locale path normalisation in
`packages/next/src/server/lib/router-utils/resolve-routes.ts` (lines
~250-263): every request that arrives without a locale prefix is
spliced with `/${defaultLocale}` before any `next.config.js`
redirect / rewrite / header rule (or filesystem route) match runs.
Without this, rules like `source: '/:locale/to-sv'` with
`locale: false` failed to match requests for `/to-sv` because there
was no segment for the `:locale` placeholder. After normalisation the
matcher sees `/en/to-sv` and the rule matches with `:locale=en`.
Per the merged PR #1382's description, the issue text's call for a 308
redirect from `/en/page` to `/page` is incorrect: Next.js serves
the same content under both URLs and normalises the path internally.
This change implements that internal normalisation; no redirect.
The new helper lives next to the existing pages i18n helpers so both
routers can use it. Wired into:
- `prod-server.ts` (Pages Router prod)
- `app-rsc-handler.ts` (App Router dev + prod)
- `index.ts` (Pages Router dev plugin)
- `deploy.ts` (Cloudflare Workers entry template)
The domain-mapped default locale (`i18n.domains[].defaultLocale`) is
preferred over the global default when the inbound host matches,
matching Next.js's `domainLocale.defaultLocale` branch in
resolve-routes.ts. `/_next/*` and `/__vinext/*` paths are excluded
to mirror Next.js's exception for build assets and prerender manifests.
The fallback "retain the original unprefixed source" branch in
`applyLocaleToRoutes` is intentionally kept as belt-and-braces: the
new normalisation pass renders it dead-code for ordinary requests, but
it harmlessly preserves backwards compatibility with any matcher
caller that still passes raw, unnormalised pathnames.
Refs #1336 (item 4)
|
||
|
|
d504f022b0 |
fix(assets): default assetsDir to _next/static (Next.js parity) (#1411)
* fix(assets): default assetsDir to _next/static (Next.js parity) Closes #1337. Supersedes #1383. vinext's default `assetsDir` was Vite's historical `assets/`, so URL emission (`/_next/static/...` via `resolveAssetUrlPrefix("")`) and on-disk layout (`dist/client/assets/...`) disagreed in the empty-`assetPrefix` case. The `build.assetsDir` and `experimental.renderBuiltUrl` Vite overrides only applied when `assetPrefix` was configured, leaving the no-prefix branch serving from a different path than the URL contract Next.js's client runtime and test harness assert against. This commit flips the default to Next.js's canonical layout: - `resolveAssetsDir("")` returns `_next/static` (was `assets`) - `build.assetsDir` is now set unconditionally from `resolveAssetsDir` - Vite's default `base + assetsDir` composition produces correct URLs in the no-prefix case; `renderBuiltUrl` stays gated on `assetPrefix` because it's only needed for the configured cases - Drops the legacy `/assets/` branches in prod-server (3 sites) and static-file-cache (hard cutover — see PR description for rationale) - Updates `_headers` generation, precompress default, fonts plugin `DEFAULT_ASSETS_DIR` to match the new default With the layout aligned, invalid `_next/static/*` requests naturally return plain-text `404 + "Not Found"` from the static-file layer instead of falling through to the page renderer (which would produce an HTML 404 with bootstrap scripts and CSS). This replaces #1383's parity short-circuit with the natural code path: - Node prod-server (App + Pages branches): missing asset under `resolveAppRouterAssetPath` returns `text/plain; charset=utf-8` 404 - Cloudflare worker entry: `isNextStaticPath` recognises asset-shape after ASSETS-binding misses; returns `notFoundStaticAssetResponse` Mirrors Next.js: packages/next/src/server/lib/router-server.ts. Adds `tests/invalid-static-asset-404.test.ts` (3 App Router + 3 Pages Router cases — no prefix / basePath / assetPrefix) ported from Next.js e2e suites. Updates 11 source files + 12 test files; 914+ tests pass in the touched suites (asset-prefix, app-router, pages-router, deploy, font-google, static-file-cache, serve-static, precompress, features, routing, isr-cache, build-optimization, app-rsc/ssr, standalone, middleware, shims). Ported from Next.js: - test/e2e/invalid-static-asset-404-app/*.test.ts - test/e2e/invalid-static-asset-404-pages/*.test.ts * fix(e2e): update cloudflare-pages-router hydration spec for _next/static * PR #1411 approved. Clean fix, 2 nits. Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> * refactor(assets): derive isHashed checks from ASSET_PREFIX_URL_DIR Replace hardcoded '_next/static' string literals in runtime code paths with the ASSET_PREFIX_URL_DIR constant so the asset URL convention has a single source of truth. - server/prod-server.ts isHashed: pathname.includes(`/${ASSET_PREFIX_URL_DIR}/`) - server/prod-server.ts public-dir guard: startsWith(`/${ASSET_PREFIX_URL_DIR}/`) - server/static-file-cache.ts isHashed: both startsWith + includes derived - plugins/fonts.ts DEFAULT_ASSETS_DIR = ASSET_PREFIX_URL_DIR Behavioural no-op — the constant is '_next/static' so the resolved strings are identical. Centralises the URL contract so future changes to ASSET_PREFIX_URL_DIR (or a refactor that derives it from build config) flow through to every consumer. Doc-comment references to '_next/static' left in place — they describe the canonical value for human readers and are not code paths. --------- Co-authored-by: ask-bonk[bot] <ask-bonk[bot]@users.noreply.github.com> Co-authored-by: james-elicx <james-elicx@users.noreply.github.com> |
||
|
|
b06a1a1d04 |
fix(i18n): strip locale prefix for API routes (#1408)
* fix(i18n): strip locale prefix for API routes Mirror Next.js's behaviour where a locale-prefixed request path like `/fr/api/ok` is matched against `pages/api/ok` after stripping the locale segment. Previously the locale prefix was kept through the `/api/` startsWith check, so any locale-prefixed API URL 404'd. Add a small helper `stripI18nLocaleForApiRoute(url, i18nConfig)` in `server/pages-i18n.ts` that mirrors Next.js's `normalizeLocalePath().pathname` (see packages/next/src/shared/lib/i18n/normalize-locale-path.ts) and wire it into all three Pages Router request pipelines so dev/prod parity holds: - dev plugin: `packages/vinext/src/index.ts` - prod (Node): `packages/vinext/src/server/prod-server.ts` - prod (Cloudflare worker entry): `packages/vinext/src/deploy.ts` App Router is intentionally unchanged: Next.js does not support the `i18n` config field with App Router, so App Router route handlers under `app/*/route.ts` never see a locale prefix to strip. Refs #1336 (item 3). Items 2 (sticky locale on client navigations) and 4 (default-locale path normalisation) are being handled in parallel PRs. Tests: - Unit tests for `stripI18nLocaleForApiRoute` covering basic, regional (`nl-NL`), query-preserving, unconfigured-prefix, and null-config cases (`tests/pages-i18n.test.ts`). - Dev integration tests (`tests/features.test.ts`) and prod integration tests (`tests/pages-i18n-prod.test.ts`) that GET `/fr/api/ok` and assert a 200 "ok" response against the existing `pages-i18n-domains` fixture, ported from Next.js's `test/e2e/middleware-redirects/test/index.test.ts` ("should redirect to api route with locale"). - Updates to `tests/deploy.test.ts` assertions for the generated worker-entry template strings. * test(after-deploy): update worker-entry assertion after locale strip The Pages Router worker entry now calls handleApiRoute(request, apiLookupUrl, ctx) instead of using resolvedUrl directly, so this generated-code assertion needs the new variable name. Refs #1336 (item 3). |
||
|
|
b7f7ad649d |
fix(basepath): enforce scoping on rewrites/redirects/routes (#1397)
* fix(basepath): enforce scoping on rewrites/redirects/routes Sub-issues addressed under #1333: 1. Rewrites/redirects/headers were firing on requests outside the configured `basePath`. Threaded a `BasePathMatchState` through the matchers and prod-server / deploy worker / dev plugin so default rules (no `basePath: false` opt-out) only evaluate when the request was under basePath, and opt-out rules only evaluate outside it. 5. Pages Router requests that landed outside basePath fell through to internal route matching. After redirects and beforeFiles rewrites run, the request now 404s when it was outside basePath and no `basePath: false` rule rewrote it — matching Next.js's `resolve-routes.ts:304-309`. 4. With rewrites no longer firing on out-of-basepath requests, the downstream code that prepends basePath to destinations no longer sees a stripped-but-already-internal pathname, eliminating the `/docs/docs/...` doubling for the rewrite-driven cases. App Router uses the same gating; `basePath: false` rules don't yet fire there because `normalizeRscRequest` 404s out-of-basepath requests before the matchers see them — flagged as follow-up in the rule gating site. External rewrite proxying (sub-issue #2) and static asset doubling (sub-issue #3, partly addressed by #1337/#1383) are deferred. Refs #1333 * test: update deploy.test signature assertions and format basepath tests * review: address bonk nits — add inverse basePath:false tests and TODO comment |
||
|
|
24a1bd4edc |
fix(middleware): redirect protocol — relative Location and x-nextjs-redirect (#1377)
* fix(middleware): redirect protocol — relative Location and x-nextjs-redirect Match Next.js's edge adapter behaviour for middleware redirects: 1. Relativize the `Location` header for same-host redirects. Next.js only emits absolute URLs when the redirect target is cross-origin; vinext was always emitting `http://host/path` for same-host targets, which broke deploy-suite parity tests. 2. Translate redirects to the `x-nextjs-redirect` soft-redirect protocol when the request carries `x-nextjs-data: 1`. The client router uses this header instead of a raw HTTP 3xx to avoid CORS issues on cross-origin data fetches. Both fixes live in `executeMiddleware`, so all four call sites (prod-server, dev plugin, deploy worker, app-middleware) inherit them. Refs #1334 * fix(middleware): thread isDataRequest through all callers Address Bonk review. The previous commit checked `x-nextjs-data` on the middleware request, but that header is in INTERNAL_HEADERS and is stripped by `filterInternalHeaders` before any caller constructs the middleware request -- so the soft-redirect branch was unreachable. Make `isDataRequest` an explicit option on `ExecuteMiddlewareOptions` and `ApplyAppMiddlewareOptions`, and capture it from the raw incoming headers in each of the four entry points (dev plugin, prod server, deploy worker, App Router RSC handler) before filtering. The generated Pages Router runMiddleware now accepts a third `options` argument carrying the flag through to the runtime helper. Also: drop redundant lowercase `headers.delete("location")` -- the Fetch spec makes Headers.delete case-insensitive. Add a regression test ensuring a forged `x-nextjs-data: 1` request header cannot trigger the soft-redirect path without the caller explicitly opting in. Refs #1334 |
||
|
|
c66b86e8f5 |
fix(after): wire next/after to Workers ctx.waitUntil in deploy mode (#1403)
Pages Router API routes did not receive the per-request Workers `ExecutionContext`, so any `after()` (or other shim) call inside a `pages/api/*` handler had no `ctx.waitUntil()` to keep the isolate alive past the response. Thread `ctx` through `handleApiRoute` → `handlePagesApiRoute`, and wrap the user handler in `runWithExecutionContext(ctx, ...)` so the ALS surfaces the ctx for downstream `after()` calls. Also pass a Node-shaped execution context from `vinext start` so behavior is consistent across the dev / start / deploy server paths. Closes #1365 |
||
|
|
afc549d624 |
fix(router): normalize trailing slash parity (#1316)
Trailing slash handling treated every non-api path the same on server redirects, so trailingSlash:true added slashes to file-looking catch-all routes and Pages Router imperative navigation skipped the client canonicalization path used by Link. Next.js splits the invariant between route-manifest redirects and client resolveHref normalization: file-looking paths lose a trailing slash, non-file paths gain one, and queries stay attached to the canonical pathname. Share the server redirect decision across Pages Router runtimes and apply the same client helper in Router.push and Router.replace. Adds focused regressions for catch-all dot segments, query preservation, .well-known exclusion, and Pages Router history writes. |
||
|
|
07be32258f |
fix(router): preserve filesystem routes before afterFiles rewrites (#1166)
* fix(router): preserve filesystem routes before afterFiles rewrites afterFiles rewrites were evaluated before App and Pages filesystem route matches in several runtime paths. That let a rewrite override an existing non-dynamic page, which diverges from Next.js route ordering. The fix checks the page/app match first and only applies afterFiles rewrites when no non-dynamic route wins, while still allowing afterFiles to run before dynamic routes. Regression coverage exercises App RSC handler, Pages dev/prod, and generated Worker wiring. * test(router): align chained afterFiles rewrite expectations The chained rewrite fixture expected an afterFiles rewrite to override a concrete /intermediate page. That is the route-ordering behavior this branch fixes. Update the fixture to cover both intended contracts: middleware can chain into afterFiles when no page file wins, and concrete page files are not overridden by afterFiles rewrites. * refactor(router): remove route-ordering type assertions Validate generated Pages route metadata at the boundary instead of asserting its shape, and make the app handler test fixture route matching explicit. * test(router): cover afterFiles order in Pages Worker Add a built Cloudflare Pages Router regression where a concrete page route must win before an afterFiles rewrite. Align custom Pages Worker entries with the generated worker route-match gate. |
||
|
|
5cf508d72d | feat(prerender): add concurrency flag (#1096) | ||
|
|
3cfccd0bd4 |
fix(deploy): resolve wrangler .CMD shim on Windows (#1098)
* fix(deploy): resolve wrangler .CMD shim on Windows (#1095) `runWranglerDeploy` invoked the bare-name `node_modules/.bin/wrangler` file, which on Windows is a Unix shebang script that CreateProcess() cannot execute, surfacing as a misleading `spawnSync wrangler ENOENT`. Prefer the `.CMD` shim on win32, fall back to the bare name on other platforms (and as an error-message fallback when nothing is found). Extracted bin resolution into an exported, platform-injected `resolveWranglerBin` helper so the behavior is unit-testable without mutating `process.platform`. Fixes #1095 * Apply suggestion from @ask-bonk[bot] Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> --------- Co-authored-by: James Anderson <james@eli.cx> Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> |
||
|
|
3f287ff5ab |
chore: remove useless code assertion tests and snaps (#1023)
* chore: remove useless code assertion tests and snaps * . |
||
|
|
9cae9cb271 | refactor: extract early request pipeline helpers (#983) | ||
|
|
33526bd89c |
fix(middleware): align cookies and external rewrites (#919)
Middleware cookies set through NextResponse only updated Set-Cookie, so cookies() in the same request could not observe values written by middleware. External middleware rewrites were normalized to pathname and search, so cross-origin destinations were routed locally instead of being proxied. Mirror middleware cookie mutations into x-middleware-set-cookie and merge that internal header back into the request cookie store. Preserve cross-origin rewrite URLs through middleware execution and proxy them across app, pages, prod, and deploy request paths, while stripping internal middleware headers from client and upstream responses. |
||
|
|
c58daa61ba |
fix: centralize protocol-relative URL guard to handle percent-encoded delimiters (#888)
Six places in the request pipeline inlined a variation of:
pathname.replaceAll("\\", "/").startsWith("//")
to reject protocol-relative paths before they reach the trailing-slash
redirect emitter (which would otherwise echo them into a Location header).
The check handles literal delimiters but not their percent-encoded forms —
`/%5Cevil.com/` survives the guard, then `normalizePathnameForRouteMatchStrict`
re-encodes the decoded backslash back to `%5C` (preserving it as part of a
single path segment), so the 308 trailing-slash redirect ends up with
`Location: /%5Cevil.com`. Browsers percent-decode Location headers and
WHATWG URL treats backslash as forward slash, so the browser resolves that
to a protocol-relative host.
Factor the leading-segment shape check into a new `isOpenRedirectShaped`
helper in `server/request-pipeline.ts` that recognises literal (`//`, `/\\`)
and percent-encoded (`%5C`, `%2F`) variants, and swap each call site over
to it. Also add a defense-in-depth check in `normalizeTrailingSlash` so a
future caller that skips the upstream guard still can't emit a bad Location.
Updated call sites:
- server/request-pipeline.ts — guardProtocolRelativeUrl + normalizeTrailingSlash
- server/prod-server.ts — App Router and Pages Router Node handlers
- server/app-router-entry.ts — default Cloudflare Worker entry
- server/app-ssr-entry.ts — SSR environment entry
- index.ts — Pages Router Vite dev middleware
- deploy.ts — generated Pages Router Cloudflare Worker (inlined, kept in sync)
Regression tests in tests/request-pipeline.test.ts cover the encoded
variants plus the defense-in-depth path. Updates the pinned string
assertion in tests/deploy.test.ts that was checking for the old inlined
pattern.
|
||
|
|
e81a6212d5 | refactor: extract remaining internal exports (#833) | ||
|
|
a5ff8653ad | refactor: remove internal re-exports from entry (#828) | ||
|
|
1dc23037b0 |
fix: serve public files in prod (#766)
* fix: serve public files in prod * fix test * resolve comments * update |
||
|
|
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 |
||
|
|
69a0cff2d5 | fix: add assets.directory to generated wrangler.jsonc (#569) | ||
|
|
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
|
||
|
|
21558f3092 |
feat: support @vitejs/plugin-react v6 as peer (#525)
* feat: support @vitejs/plugin-react v6 as peer * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update |
||
|
|
b191aff91d |
fix: TPR zone resolution for multi-part TLD domains (#470)
* fix: TPR zone resolution for multi-part TLD domains Replace hardcoded 12-entry MULTI_PART_TLDS list with progressive domain candidate lookup. For "shop.example.co.uk", tries co.uk → example.co.uk → shop.example.co.uk until a Cloudflare zone matches. This fixes zone resolution for all public suffixes (.org.au, .co.in, .gov.uk, etc.) without maintaining a TLD list. Common TLDs (.com, .io) still resolve on the first API call. Also fixes the missing fallback — the previous code returned null on miss despite a TODO comment claiming fallback behavior existed. * chore: format test file |
||
|
|
16a14c713b |
fix: Pages prod config headers and redirects after middleware rewrites (#448)
* Fix headers matching original path * Review redirect ordering parity * Fix config redirects order * Fix dev middleware parity * Fix dev middleware request parity * address review: worker header/fallback parity, early-return in attachRouteHandlerMiddlewareContext, cache node request headers, add dev redirect test * address second-round bonk comments: sanitizeDestination in worker redirect, nodeRequestHeaders comment, app-rsc-entry guard comment, E2E redirect test, updated snapshots --------- Co-authored-by: James <james@eli.cx> |
||
|
|
319f9296ff |
feat: ISR caching for App Router (production-only, stale-while-revalidate) (#405)
* feat: add ISR caching for App Router (production-only, stale-while-revalidate)
- Inline ISR helpers (__isrGet, __isrSet, __triggerBackgroundRegeneration,
__isrCacheKey, __pendingRegenerations) in the generated RSC entry
- Cache READ fires before buildPageElement for prod pages with revalidate > 0,
returning cached HTML/headers without loading component modules on HIT/STALE
- Cache WRITE (+ X-Vinext-Cache: MISS header) is guarded by
process.env.NODE_ENV === 'production' so dev never reads/writes the cache
- Background regenration is deduplicated via __pendingRegenerations Map and
registered with ctx.waitUntil for Cloudflare Workers
- Thread ExecutionContext (ctx) from app-router-entry.ts through to _handleRequest
- Dev mode still emits correct Cache-Control headers based on export const revalidate
- Add integration tests: Cache-Control in dev, no X-Vinext-Cache in dev, RSC
requests bypass ISR, pages without revalidate emit no ISR headers
- Add 10 code-generation tests verifying the prod guard, helpers, and structure
* fix: remove unused isrCachePath variable and fix formatting
* fix: cache RSC wire format (rscData) in ISR entries; serve RSC requests from cache
Previously the ISR cache only stored HTML and excluded all RSC requests
from cache reads/writes. This caused two bugs:
1. RSC requests (client-side navigation, link prefetch) always triggered a
full component tree render even when a fresh cache entry existed, wasting
CPU on every soft-nav and prefetch.
2. rscData was never captured — the cache entry had rscData: undefined —
so there was no way to serve RSC requests from cache even if we wanted to.
Fixes:
- Tee the RSC stream before SSR on ISR-eligible HTML requests (prod + revalidate > 0)
to capture the RSC wire format as an ArrayBuffer stored in rscData
- Background regen also tees its RSC stream to keep rscData fresh
- Remove the !isRscRequest guard from the ISR READ block: both HTML requests
and RSC requests now hit the cache. HTML requests get cached html; RSC
requests get cached rscData (falling through to render if rscData is absent)
- HIT and STALE paths branch on isRscRequest: RSC responses get
Content-Type: text/x-component and the rscData bytes; HTML responses get
Content-Type: text/html and the html string
Tests updated:
- Replace 'RSC requests return RSC stream without cache headers' test with
two tests that assert Cache-Control IS emitted for RSC responses on ISR
pages (dev mode), and that Next-Router-Prefetch requests also get it
- Replace .tee() count test with assertion that >=2 tees exist (RSC + HTML)
- Add rscData storage assertions for WRITE and background regen paths
- Add assertion that ISR READ block has no !isRscRequest guard
* fix(isr): fix RSC cache miss and early-exit performance for App Router ISR
Two bugs fixed:
1. RSC requests (client-side nav / prefetch) were never cached because the RSC
stream tee that captures rscData was placed AFTER the 'if (isRscRequest)'
early-return, so the capture branch was never consumed. Move the tee to
immediately after renderToReadableStream — before the isRscRequest branch —
and register a waitUntil in the RSC path to write rscData to the cache.
2. The ISR cache read block fired after the generateStaticParams call, meaning
a cache hit still did the expensive static-params validation. Move the ISR
read to before generateStaticParams so a HIT skips all that work.
Tests: add two new generateRscEntry ISR assertions:
- ISR read index < generateStaticParams index in generated code
- __rscForResponse tee assignment before 'return new Response(__rscForResponse'
* feat(isr): cache RSC prefetch responses immediately as partial entries
RSC prefetch requests (Next-Router-Prefetch and client-side nav) now write
a partial cache entry with rscData + html:'' on MISS. Subsequent RSC requests
serve from cache (HIT) without waiting for an HTML request to populate the
entry first.
The ISR read block treats html:'' as a partial hit: RSC requests get a HIT,
but HTML requests fall through to render and overwrite the entry with a
complete html+rscData entry. This prevents blank HTML responses on first HTML
request after a prefetch-first cold start.
Also adds console.log logging for all ISR cache events (HIT/MISS/STALE/write)
in production to aid debugging on deployed Workers.
* fix(isr): use ALS to thread ExecutionContext into KVCacheHandler without constructor arg
Add runWithExecutionContext/getRequestExecutionContext to next/cache shim
backed by AsyncLocalStorage. The RSC entry handler now wraps each request
in this ALS scope so KVCacheHandler._putInBackground and _deleteInBackground
can call ctx.waitUntil() without needing ctx passed at construction time.
- cache.ts: add ExecutionContextLike interface + ALS helpers
- app-rsc-entry.ts: import runWithExecutionContext, wrap _run in ALS scope
- kv-cache-handler.ts: check ALS ctx first, fall back to constructor ctx
- deploy.ts: simplify generated template to new KVCacheHandler(env.VINEXT_CACHE)
- tests/deploy.test.ts: update expectation to match simplified constructor
* fix(isr): store activeHandler on globalThis to fix cross-environment cache isolation
Cloudflare Workers with Vite RSC loads separate module instances per
environment (worker / RSC / SSR). Previously, activeHandler was a
module-local variable, so setCacheHandler(new KVCacheHandler(...)) in
the worker environment set the worker copy while getCacheHandler() in
the RSC environment still returned a fresh MemoryCacheHandler from
its own copy — causing every ISR read/write to go to in-process
memory that is discarded after each request.
Fix: store the active handler on globalThis via Symbol.for('vinext.cacheHandler'),
the same pattern used by _cacheAls, _ctxAls, and _unstableCacheAls. All
Vite environments share the same globalThis on Cloudflare Workers, so
the KVCacheHandler set in worker/index.ts is now visible to the RSC
entry's getCacheHandler() call, enabling real KV-backed ISR cache
reads and writes.
* fix(isr): allow force-static/error pages to be served from ISR cache, prevent partial RSC write from clobbering complete entry
- Remove !isForceStatic and !isDynamicError from the ISR cache read guard.
Both modes are compatible with ISR: they control dynamic API behaviour during
render, not whether results can be cached. The asymmetry between the read guard
(which excluded them) and the write guard (which did not) caused writes to
succeed but reads to be skipped entirely, so every request returned MISS.
Only isForceDynamic should bypass the ISR cache.
- Add a read-before-write in the RSC partial-write path. When an RSC request
writes a partial cache entry (html: "", rscData set) before the first HTML
request has come in, a concurrent or later RSC request could race and overwrite
a complete html+rscData entry that the HTML write had already landed. The fix
reads the existing entry first and skips the partial write if a fresh, complete
entry (non-empty html) is already present, so the HTML write always wins.
* fix(isr): split HTML and RSC into separate cache keys, eliminating write races
Previously both HTML and RSC data were stored under a single key with an
html:"" partial-entry sentinel to signal 'RSC written, HTML pending'. This
caused write races on eventually-consistent stores like KV: a concurrent RSC
request could overwrite a complete html+rscData entry with a partial one,
or the read-before-write guard added to prevent that would itself race.
This mirrors how Next.js handles it in FileSystemCache: HTML is stored in
<key>.html and RSC in <key>.rsc as independent files, so each request type
reads and writes only its own key with no coordination needed.
Changes:
- Replace __isrCacheKey() with __isrHtmlKey() / __isrRscKey() which append
:html and :rsc suffixes respectively
- ISR read block selects the key based on isRscRequest before the get() call
- RSC write path uses __isrRscKey and stores only rscData (html: "")
- HTML write path uses __isrHtmlKey and stores only html (rscData: undefined)
- Background regen writes both keys independently via Promise.all
- Remove the read-before-write hack (no longer needed)
- Remove partial-entry sentinel comments (concept no longer exists)
- HTML write path no longer awaits __isrRscDataPromise
* fix(kv-cache): always use 30-day KV TTL regardless of revalidate frequency
Previously expirationTtl was 10x the revalidate period (clamped to 60s–30d).
For pages with short revalidate windows (e.g. revalidate=5), this meant entries
were evicted from KV after ~50 seconds of no traffic, causing the next request
to block on a fresh render instead of serving stale content.
KV eviction should never be the reason a stale entry disappears — staleness is
tracked by the revalidateAt timestamp in the stored JSON, not by KV TTL. Always
storing for 30 days means stale content is always available to serve while
background regen runs, and entries only disappear after 30 days of zero traffic
or explicit tag invalidation.
* fix(isr): prefix KV cache keys with per-deployment build ID, add ttlSeconds option to KVCacheHandler
- __isrCacheKey now embeds process.env.__VINEXT_BUILD_ID (statically
replaced by Vite's define at build time) so each deployment gets its
own effective cache namespace in KV — stale entries from previous
deploys are never served as hits
- vinextBuildId (crypto.randomUUID() at plugin scope) is passed to
generateRscEntry and injected as the __VINEXT_BUILD_ID define
- KVCacheHandler accepts an optional ttlSeconds constructor option
(defaults to 30 days) so users can override KV entry lifetime
* regen snaps
* fmt
* refactor(ctx): migrate to canonical request-context shim for ExecutionContext ALS
- shims/cache.ts: remove duplicated ALS implementation; re-export
runWithExecutionContext and getRequestExecutionContext from the
canonical shims/request-context.ts (Symbol.for('vinext.requestContext.als'))
- app-rsc-entry.ts: import runWithExecutionContext from request-context
via absolute path (consistent with pages-server-entry pattern) rather
than the bare 'vinext/shims/request-context' specifier which is not
in the alias map
- deploy.ts: restore missing ${isrSetup} interpolation that was dropped
during rebase conflict resolution
- Regenerate entry-template snapshots
* fix(isr): address PR review — hash collision, dedup key, regen headers, RSC key from HTML path, Infinity revalidate, Response init, deploy KV hint
* fix(isr): use __hasRsc/__hasHtml guards in cache read block; document 30-day flat KV TTL in test
* fix(isr): gate debug logs behind NEXT_PRIVATE_DEBUG_CACHE, add FNV seed comment, document headers limitation
|
||
|
|
f72440f082 |
fix: Pages Router prod hydration for inlined page modules (#394)
* Clarify chunk manifest structures * Fix SSR manifest basePath URLs * Fix SSR manifest basePath handling * Fix SSR manifest basePath handling * make ci happy * bonk |
||
|
|
36f1775122 |
feat: AsyncLocalStorage for ExecutionContext (ctx) propagation (#410)
Adds request-context.ts shim with runWithExecutionContext() and
getRequestExecutionContext() so Cloudflare Workers ctx is accessible
anywhere in the call stack without manual threading.
- New shims/request-context.ts with ALS keyed on Symbol.for('vinext.requestContext.als')
- app-router-entry.ts wraps each request in runWithExecutionContext(ctx, ...)
- app-rsc-entry.ts generated code imports the helpers for downstream use
- deploy.ts generated worker entry forwards ctx in fetch(request, env, ctx)
- kv-cache-handler.ts uses getRequestExecutionContext() ?? this.ctx
- 8 new tests in tests/request-context.test.ts
|
||
|
|
dea3c3e44f |
fix: enforce segment boundaries for basePath stripping and redirect prefixing (#393)
* Update deploy basePath stripping * Fix redirect basePath handling * Document basePath segment fix * Fix external redirect basePath |
||
|
|
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 |
||
|
|
405e46af1d |
fix: preserve middleware redirect headers in production (#365)
* fix: preserve middleware redirect headers (Set-Cookie) in production middleware.ts correctly collects non-internal headers (e.g. Set-Cookie) on redirect responses, but three consumers dropped them: - prod-server.ts: sent only Location header, ignored responseHeaders - deploy.ts: worker entry sent only Location header - index.ts: inline runMiddleware codegen didn't collect headers on redirect (it already did for next() and rewrite(), just not redirect) The dev server (index.ts:2934) handled this correctly, making this a dev/prod parity bug where cookies set during middleware redirects worked in development but were silently dropped in production. * test: update entry template snapshot for redirect header fix --------- Co-authored-by: James <james@eli.cx> |
||
|
|
1a2fd61092 |
fix: fail build when cloudflare() plugin is missing (#358)
* fix: fail build when cloudflare() plugin is missing Upgrade the deploy-time warning to a hard error and add a build-time check in configResolved that detects a wrangler config without the cloudflare() plugin registered. Previously, forgetting to add cloudflare() to vite.config.ts produced a cryptic "could not resolve virtual:vinext-rsc-entry" error deep in wrangler esbuild. Now the build fails early with an actionable message showing exactly what to add. The validation only fires during builds (not dev) and is skipped when Nitro is the deployment target. Closes #325 * refactor: extract shared utilities for cloudflare plugin guard Address code review feedback on PR #358: - Extract `hasWranglerConfig(root)` utility from `detectProject()` inline check, reuse in both `deploy.ts` and `index.ts:configResolved` - Extract `formatMissingCloudflarePluginError({ isAppRouter, configFile? })` shared error formatter used by both build-time and deploy-time guards - Fix hardcoded `vite.config.ts` references: build-time guard now uses `config.configFile`, deploy-time guard uses generic "your Vite config" - Add 10 unit tests covering both new utilities * Update packages/vinext/src/deploy.ts Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> --------- Co-authored-by: James Anderson <james@eli.cx> Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> |
||
|
|
faca3dd663 |
fix(cli): ensure type:module before Vite loads vite.config.ts (fixes #184) (#197)
Projects that lack "type":"module" in package.json caused Vite to bundle vite.config.ts through esbuild's CJS path, producing a .vite-temp/*.mjs file that contains a CJS require() for @cloudflare/vite-plugin. On Node 22 in CI (GitHub Actions ubuntu-latest), require()-ing an .mjs file is not supported and the build fails with: Error: Dynamic require of "…/@cloudflare/vite-plugin/dist/index.mjs" is not supported Fix: call ensureESModule(root) + renameCJSConfigs(root) in both the dev() and buildApp() CLI commands, before Vite is loaded. This mirrors what vinext init already does, but applies lazily for projects that were set up manually or before the init step was introduced. - renameCJSConfigs renames postcss.config.js / tailwind.config.js / .eslintrc.js etc. to .cjs first, to avoid breakage in those files after ESM mode is enabled - ensureESModule adds "type":"module" to package.json - Both are no-ops when the project is already correctly configured - A warning is emitted when a change is made, pointing users to vinext init - 9 new unit tests cover the full scenario including multi-file rename Fixes #184 |
||
|
|
2f21bb3a7d |
fix: pass NextFetchEvent as second argument to middleware (#340)
* fix: pass NextFetchEvent as second argument to middleware
All 3 middleware invocation sites were calling `middlewareFn(nextRequest)`
with only one argument. The `NextMiddleware` type expects `(request, event)`
but `NextFetchEvent` was never instantiated or passed. Any middleware using
`event.waitUntil()` would throw at runtime because `event` is `undefined`.
Fix: instantiate `NextFetchEvent({ page })` at each call site, pass it as
the second argument, and drain `_waitUntilPromises` via
`Promise.allSettled()` (fire-and-forget) after middleware returns.
Call sites fixed:
- `packages/vinext/src/server/middleware.ts` (shared runner, used by prod)
- `packages/vinext/src/index.ts` (Pages Router dev codegen)
- `packages/vinext/src/server/app-dev-server.ts` (App Router dev codegen)
* address review: add drainWaitUntil(), wire ctx.waitUntil() in Workers entry, fix async test
* fix deploy tests: update assertions for ctx-aware fetch and runMiddleware signatures
* address review: fix misleading comment, update JSDoc, use queueMicrotask in test
* pass undefined explicitly to runMiddleware in prod-server (no ctx on Node.js)
---------
Co-authored-by: James <james@eli.cx>
|
||
|
|
70b18536ab |
fix: warn when existing vite.config.ts is missing cloudflare plugin on deploy (#315)
* fix: warn when existing vite.config.ts is missing cloudflare plugin on deploy Closes #312. - Add `viteConfigHasCloudflarePlugin()` to deploy.ts: when `vinext deploy` finds an existing vite.config.ts that doesn't import @cloudflare/vite-plugin, it now prints an actionable warning with the exact config fix rather than silently proceeding to a broken build. - Add README prerequisites section to the Cloudflare Workers deploy docs: covers wrangler login vs CLOUDFLARE_API_TOKEN (with required permissions) and how to supply account_id. - Add 7 unit tests for `viteConfigHasCloudflarePlugin()`. * fix: pages router warning shows cloudflare() not cloudflare(()) |
||
|
|
09d9d4cde0 |
refactor: extract applyMiddlewareRequestHeaders into config-matchers (#305)
* refactor: extract applyMiddlewareRequestHeaders into config-matchers Move the x-middleware-request-* unpacking + postMwReqCtx rebuild logic out of prod-server.ts and deploy.ts and into a shared applyMiddlewareRequestHeaders() in config-matchers.ts, eliminating ~30 lines of duplicated code from each callsite. * fix(test): update deploy test to reflect applyMiddlewareRequestHeaders refactor |
||
|
|
c03cb3f4b4 |
fix(deploy): monorepo-aware lock file and node_modules resolution (#122)
* fix(deploy): monorepo-aware lock file and node_modules resolution
vinext deploy failed in monorepos in two ways:
1. Wrong package manager detected — detectPackageManager only checked for
bun.lockb (legacy binary format), missing bun.lock (text format, Bun
v1.0+). It also only searched the immediate project directory, so any
monorepo where the lock file lives at the workspace root fell through to
npm, causing a confusing "npm error Invalid Version:" failure.
2. Dependency detection and wrangler binary path hard-coded to the app
directory — hasCloudflarePlugin, hasRscPlugin, hasWrangler, hasMdxRollup
all checked {root}/node_modules only, producing false negatives for
hoisted packages. runWranglerDeploy then crashed with ENOENT because
the wrangler binary was at the workspace root, not the app root.
Changes:
- Add walkUpUntil<T>(start, check) to utils/project.ts — a shared primitive
that walks ancestor directories until check() returns non-null. Both the
lock-file and node_modules walkers are built on top of this.
- Rewrite detectPackageManager / detectPackageManagerName using walkUpUntil;
add bun.lock (text) check alongside existing bun.lockb (binary).
- Add findInNodeModules(start, subPath) using walkUpUntil — walks up to find
node_modules/{subPath}, returning the absolute path or null.
- Use findInNodeModules in detectProject (hasCloudflarePlugin, hasRscPlugin,
hasWrangler), getMissingDeps (hasMdxRollup), and runWranglerDeploy.
Tests (12 new):
- detectPackageManager: pnpm, yarn, bun.lock, bun.lockb, npm fallback,
monorepo root traversal, closest-wins precedence
- findInNodeModules: package lookup, binary lookup, null fallback,
monorepo root traversal, closest-wins precedence
* fix(test): clear npm_config_user_agent in npm fallback test
On CI, pnpm sets process.env.npm_config_user_agent which causes
detectPackageManagerName to return 'pnpm' instead of falling through
to the npm default. Save and restore the env var around the assertion.
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
|
||
|
|
af0a839a67 |
fix: preserve multiple Set-Cookie headers in response merging (#297)
* fix: preserve multiple Set-Cookie headers in prod-server and worker entry The response header merging in prod-server.ts and the generated Cloudflare worker entry used Record<string, string> which flattened multiple Set-Cookie headers — the last value won, or cookies with Expires dates got corrupted by comma-joining. Extract mergeResponseHeaders() helper that uses getSetCookie() to preserve array-valued Set-Cookie headers. Apply the same fix to the worker entry template in deploy.ts using the Headers API. Fixes #295 * fix: address review feedback on Set-Cookie header preservation - Add `as string` cast in deploy.ts worker entry for x-middleware-request-* header unpacking (matches prod-server.ts) - Normalize Vary header with Array.isArray check in sendCompressed to handle the widened type correctly - Add edge-case test for middleware cookie as plain string (not array) * fix: apply Set-Cookie fix to example workers, remove redundant toLowerCase - Fix the same Set-Cookie flattening bug in hand-written example worker entries (pages-router-cloudflare, realworld-api-rest) - Remove 4 redundant .toLowerCase() calls — Headers.forEach() always yields lowercase keys * fix: add missing as string cast in deploy.ts middleware header collection * refactor: extract mergeHeaders to shared vinext/server/worker-utils The mergeHeaders function was duplicated in both example worker entries (pages-router-cloudflare, realworld-api-rest) and the deploy.ts template. Extract it to a shared module that example workers import. The deploy.ts template still inlines it (code generation constraint). Reduces 3 copies to 2 (shared module + generated template). * fix: array-accumulate Set-Cookie in config headers, fix JSDoc, add behavioral test - deploy.ts template: config headers section was comma-joining Set-Cookie values using += which corrupts cookies with Expires dates and loses all but the last cookie when multiple config rules match. Matches the array-accumulation pattern already used in prod-server.ts (lines 899-907) and the middleware section above. - worker-utils.ts + deploy.ts template JSDoc: 'Response headers take precedence' was misleading — Set-Cookie is additive, not overriding. Clarify both docs. - tests/deploy.test.ts: add behavioral test for mergeHeaders via worker-utils import (same function inlined in the generated template), replacing reliance on string-contains assertions alone. * fix: indentation in deploy.ts template, array-accumulate Set-Cookie in example workers - deploy.ts: fix extra leading space in config headers block (lines 636-659) and JSDoc lines 740-743 introduced in previous commit - pages-router-cloudflare, realworld-api-rest: config headers section was doing a plain assignment (middlewareHeaders[lk] = h.value) which overwrites array-valued Set-Cookie built up by the middleware section above; use the same array-accumulation pattern as prod-server.ts and the deploy.ts template * fix: remove as any casts in prod-server.ts, add Vary handling to example workers - prod-server.ts lines 904/906: middlewareHeaders is now Record<string, string | string[]> so the as any casts are unnecessary; replace with as string (consistent with the middleware collection section above at line 848) - pages-router-cloudflare, realworld-api-rest: add Vary comma-joining to config headers section to match prod-server.ts (line 908) and deploy.ts template (line 653) --------- Co-authored-by: James <james@eli.cx> |
||
|
|
fe0a48dde4 |
fix: lowercase package name before sanitizing Worker name (#159)
* fix: lowercase package name before sanitizing Worker name
* test: assert exact sanitized project name value
Strengthen the sanitization test to assert the exact expected value
('my-app-v2') instead of just matching a regex pattern. The regex
assertion passed even when the bug was present.
---------
Co-authored-by: Steve Faulkner <sfaulkner@cloudflare.com>
|
||
|
|
b70f4037fb |
fix: preserve x-middleware-request-* headers in generated runMiddleware (#175)
* fix: preserve middleware request override headers in runMiddleware * Strip x-middleware-* headers, detect bun, update fixtures Preserve and unpack x-middleware-request-* headers while removing any other x-middleware-* headers in the worker entry (defense-in-depth). Add bun package manager detection in project utils (detectPackageManager and detectPackageManagerName). Update tests to assert the new header-stripping behavior. Update pages-basic fixture entry: add explicit void for promise calls, inject a middleware header override route, add dangerous-scheme blocking for Link hrefs, include credentials on prefetch, update dynamic import asset names and file metadata, and rename several getServerSideProps helper identifiers to avoid collisions. * Normalize absolute file paths in fixture Update fileName annotations in tests/fixtures/pages-basic/dist/server/entry.js to replace local developer paths (/Users/sunilpai/...) with CI runner paths (/home/runner/work/...). This cleans up generated JSXDEV metadata in the compiled test fixture to avoid leaking local machine paths and make outputs reproducible. --------- Co-authored-by: Sunil Pai <spai@cloudflare.com> |
||
|
|
75703d3b72 |
Align Pages Router worker entry with prod-server request handling (#171)
* Skip deploy previews for fork PRs that lack Cloudflare secrets Fork PRs don't have access to repository secrets (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID), so deploy/smoke-test/comment steps always fail. Add a fork detection condition to skip these steps gracefully. The build steps still run, so example builds are still validated for fork PRs. * fix: align Pages Router worker entry with prod-server request handling The generated Cloudflare Worker entry for Pages Router apps was missing several request handling steps that the Node.js production server (prod-server.ts) already handled. This brings the two in sync: - Run middleware (runMiddleware) before routing - Apply next.config.js redirects, rewrites (before/after/fallback), and headers - Handle basePath stripping and trailing slash normalization - Merge middleware response headers with correct precedence - Guard renderPage with typeof check Also updates the two Pages Router examples (pages-router-cloudflare, realworld-api-rest) and exports vinext/config/config-matchers so the worker entry can import the shared matching utilities. * chore: sync pnpm-lock.yaml with upstream package.json changes |