Skip to content

refactor(pages-router): unify triplicated request pipeline into runPagesRequest (#1782)#1860

Merged
james-elicx merged 11 commits into
cloudflare:mainfrom
Divkix:refactor/pages-request-pipeline
Jun 9, 2026
Merged

refactor(pages-router): unify triplicated request pipeline into runPagesRequest (#1782)#1860
james-elicx merged 11 commits into
cloudflare:mainfrom
Divkix:refactor/pages-request-pipeline

Conversation

@Divkix

@Divkix Divkix commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closes #1782.

The Pages Router request sequence was copy-pasted three times — prod-server.ts (Node), deploy.ts (Cloudflare Worker template), and index.ts (Vite dev middleware). This PR collapses all three into one owner that the adapters delegate to.

What changed

New: server/pages-request-pipeline.ts exports runPagesRequest(request, deps) which owns the canonical Next.js 9-step ordering once. Returns a PagesPipelineResult discriminated union: {type:"response"} for every early return + prod/worker render, {type:"render"|"api"} intents when render callbacks are absent (dev). Mirrors createAppRscHandler's thin-closure injection pattern.

Migrated:

  • prod-server.ts startPagesRouterServer — keeps Node-specific concerns: decode/400, _next/data normalization, static assets, image opt, compression sink. Delegates the 9-step ordering.
  • deploy.ts worker template — keeps worker-specific concerns: registerConfiguredCacheAdapters, static-asset 404, image bindings. Delegates the 9-step ordering.
  • index.ts dev middleware — keeps Vite-specific concerns: Vite skips, cross-origin guard, image 302, basePath strip, _next/data normalization. Uses a devRunMiddlewareAdapter wrapper to bridge the dev runner API to MiddlewareResult.

Deleted:

  • applyRedirects, applyRewrites, applyHeaders (parallel Node-res helpers at index.ts:4829-5015) — these were the exact three functions the issue called out.
  • proxyExternalRewriteNode — external rewrites now return {type:"response"} from the pipeline.

Behavioral decisions

Preserved as-is (out of scope): Config-headers-after-middleware timing gap; external-proxy merge asymmetry (merge only on post-middleware rewrite path, not on beforeFiles/afterFiles/fallback external paths — both prod and worker already agreed on this).

Reconciled (latent worker drift fixed): Two subtle differences between the prod and worker copies were unified to the superset:

  • middlewareStatus = result.status ?? result.rewriteStatus (worker previously dropped result.status)
  • preserveCredentialHeaders: isExternalUrl(resolvedUrl) passed to applyMiddlewareRequestHeaders (worker previously omitted this)

renderPage signature extension: Added optional stagedHeaders?: Headers parameter so the prod adapter can pass pre-staged middleware headers to the SSR renderer for CSP nonce injection — same information that was previously passed as middlewareResponseHeaders in the old inline code.

Tests

  • tests/pages-request-pipeline.test.ts (new, 28 tests): drives runPagesRequest directly. Covers step order, redirect 307/308 + query preservation, middleware short-circuits + multi-value Set-Cookie, before/after/fallback rewrites + external proxy, out-of-basePath 404, trailing-slash, staged-header precedence, reconciled deltas, and render/api intent emission.
  • All existing tests pass: 276 pages-router, 112 routing, 245 deploy, 28 pipeline, 36 request-pipeline.

Line counts (net)

File Before After Delta
prod-server.ts 2124 ~1680 −444
deploy.ts 1630 ~1280 −350
index.ts 5157 ~4460 −697
pages-request-pipeline.ts 478 +478
pages-request-pipeline.test.ts 563 +563

Total: net ~−450 lines, with clear ownership and focused tests.

Divkix added 5 commits June 8, 2026 14:54
…ests

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.
…sRequest

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.
… 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.
…te 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.
@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@1860
npm i https://pkg.pr.new/vinext@1860

commit: e255ac8

Divkix added 2 commits June 8, 2026 15:51
…y/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.
@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the refactor end-to-end against the three original copies (prod-server, worker template, dev middleware) and the new runPagesRequest owner. The consolidation is well-structured, the discriminated-union intent design is clean, and the new pipeline test suite is thorough. Ran tests/pages-request-pipeline.test.ts (28 ✓), tests/deploy.test.ts (245 ✓), tests/after-deploy.test.ts (9 ✓), and the pages-router content-type/octet-stream cases — all green.

The documented reconciliations (result.status ?? result.rewriteStatus and preserveCredentialHeaders on the worker path) and the body/headers preservation through applyMiddlewareRequestHeaders for external proxying both check out. Dev hybrid hasAppDir gating and afterFiles/fallback ordering are preserved.

One genuine behavioral divergence to flag (non-blocking), plus a couple of minor notes below.

const responseHeaders = mergeResponseHeaders({}, mergedResponse);
const finalStatusText = mergedResponse.statusText || undefined;
const responseBody = Buffer.from(await response.arrayBuffer());
const ct = response.headers.get("content-type") ?? "text/html";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavioral regression: lost application/octet-stream default for API routes.

The pre-refactor prod-server had two distinct content-type fallbacks: API routes defaulted to application/octet-stream (old line 1945, with an explicit comment: "API routes may return arbitrary data (JSON, binary, etc.), so default to application/octet-stream rather than text/html when the handler doesn't set an explicit Content-Type"), while page routes defaulted to text/html (old line 2068).

This unified branch now handles both API and page responses with a single ?? "text/html" fallback. For an API response that reaches the adapter with no content-type header, the body is now served as text/html instead of application/octet-stream — the exact content-sniffing/XSS hazard the original code deliberately avoided.

In practice api-handler.ts sets sensible defaults (text/plain/octet-stream/json) for res.end(...)/res.send(...), so the existing tests/pages-router.test.ts:4547 (not.toContain("text/html")) still passes. But the safety net for a truly content-type-less API response is gone. Since the pipeline's {type:"response"} result doesn't distinguish API vs page, consider either tagging the API path in the result or restoring the octet-stream default for non-HTML responses. At minimum this divergence should be called out in the PR's "Behavioral decisions" section rather than dropped silently.

requestHeaders: Headers;
middlewareStatus: number | undefined;
}
| { type: "next" }; // dev passthrough (no matching route, has appDir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ type: "next" } is declared in the result union but runPagesRequest never returns it — the dev next() fallthrough is decided in the adapter (index.ts:3598/3583) via hasAppDir after the render/api intent. The prod/worker adapters also have dead "render"|"api"|"next" guard branches.

This is fine as forward-compat surface, but worth a one-line comment here noting the variant is currently unused (reserved for a future dev passthrough) so a reader doesn't go hunting for where it's produced.

}

// Step 8: External-URL proxy (post-mw rewrite target)
if (isExternalUrl(resolvedUrl)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: the external-proxy merge asymmetry is preserved here (post-middleware rewrite path merges middleware headers via mergeHeaders(proxyResponse, middlewareHeaders, undefined), while the beforeFiles/afterFiles/fallback external paths at lines 337/389/435/478 return the raw proxyExternal(...) response without merging). The PR body documents this as intentional ("both prod and worker already agreed on this"), which matches what I see in the pre-refactor copies — just confirming it's deliberate and noting a short inline comment on the bare proxyExternal returns would help future maintainers not "fix" it into an accidental divergence.

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review posted successfully to PR #1860.

Summary

I reviewed the refactor against all three original copies (prod-server, worker template, dev middleware) and the new runPagesRequest owner, and ran the relevant test files (28 + 245 + 9 + pages-router content-type cases — all passing).

Overall: Solid consolidation. The discriminated-union intent design, the documented reconciliations (result.status ?? result.rewriteStatus, preserveCredentialHeaders), body/header preservation through applyMiddlewareRequestHeaders, and dev hybrid hasAppDir/afterFiles/fallback ordering all check out.

Findings posted (3 inline comments, none blocking):

  1. Content-type regression (prod-server.ts:1674) — The most substantive issue. The pre-refactor prod-server had two distinct fallbacks: application/octet-stream for API routes and text/html for pages. The unified branch collapses both into ?? "text/html". api-handler.ts sets defaults in common cases so existing tests pass, but the deliberate safety default for content-type-less API responses is lost. Worth either restoring or documenting in the "Behavioral decisions" section.

  2. Unused {type:"next"} variant (pipeline:128) — Declared in the union but never produced by runPagesRequest; suggest a comment noting it's reserved.

  3. External-proxy merge asymmetry (pipeline:318) — Confirmed intentional and matching the originals; suggested an inline comment to prevent a future accidental "fix."

github run

…te 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).
@james-elicx

Copy link
Copy Markdown
Member

Addressed the bonk findings plus two further regressions found while reviewing the extraction against all three original copies. Pushed in 568dead.

Bonk findings

  1. Content-type regression (prod-server.ts) — restored. The pipeline now tags API results with isApiResponse, and the Node adapter defaults a missing content-type to application/octet-stream (arbitrary data) instead of text/html. Page renders still default to text/html.
  2. Unused {type:"next"} — added a comment noting it's reserved forward-compat surface; the dev passthrough is decided in the adapter via hasAppDir.
  3. External-proxy merge asymmetry — added inline comments on each bare proxyExternal(...) return pointing to the Step 8 asymmetry note, so it isn't "fixed" into an accidental divergence.

Two additional regressions fixed
4. Public-directory static serving dropped (prod-server.ts, Node) — the original ran a post-middleware tryServeStatic (step 5b) for public/ files (favicon.ico, robots.txt, …); the migrated adapter lost it, so those requests fell through to the renderer and 404'd. Restored via a serveStaticFile dep the pipeline calls post-middleware/pre-rewrites, with a new {type:"handled"} result for callbacks that write their own output.
5. Hybrid app+pages dev: app/api/* lost middleware context — the dev api intent ran flushStagedHeaders()/flushRequestHeaders() unconditionally before discovering no pages-api matched, wiping req.headers (incl. VINEXT_MW_CTX_HEADER) on the next() fallthrough. Gated both behind the pages-api match, mirroring the original if (apiMatch) guard.

Tests: +5 pipeline cases (serveStaticFile/handled ordering, isApiResponse tag); 33 pipeline, 276 pages-router, 245 deploy, 9 after-deploy, 112 routing all pass locally. vp check clean (pre-existing env module-resolution errors only).

@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

…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.
…ct 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.
@james-elicx

Copy link
Copy Markdown
Member

Addressed the remaining code-review findings in dba28a6 (the two worker divergences were already fixed in 448764d):

  1. Content-type on passthrough responses (prod) — middleware short-circuits and external-proxy responses with a body but no Content-Type no longer get a text/html default injected. Generalized the response tag to defaultContentType (render → text/html, api → application/octet-stream, passthrough → unset); the Node adapter sends untagged responses verbatim via sendWebResponse, matching pre-refactor behavior.

  2. Redirect query re-encoding (prod + dev) — config-redirect Location now uses the raw req.url query (new rawSearch dep) instead of new URL().search, which re-encoded "/</> and truncated at a literal #. Confirmed the original prod and dev both used the raw query (applyRedirects(..., url.slice(indexOf('?')))); the worker keeps url.search (it only ever had a Web Request).

  3. matchPageRoute computed twice per request — now reuses the Step 12 match unless an afterFiles rewrite actually changed the pathname.

  4. mergeWebResponse / mergeHeaders duplicationmergeWebResponse was dead on the request path (the pipeline uses mergeHeaders); collapsed it into a thin delegation to the canonical mergeHeaders and removed the now-orphaned hasHeader/stripHeaders helpers. The existing mergeWebResponse tests validate the delegation is behavior-identical.

3 review candidates were verified and dropped as refuted: dev req.url staleness on app-passthrough (the app router reads the rewrite from the x-vinext-mw-ctx r field, not req.url), dev next.config headers on app-passthrough (the App Router's own finalizer re-applies them), and dev proxyExternal header/query forwarding (it correctly uses the pipeline's post-middleware request).

Tests: 36 pipeline + 309 features + 276 pages-router + 245 deploy + 9 after-deploy + 112 routing pass; vp check clean.

@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

# Conflicts:
#	packages/vinext/src/index.ts
@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the full refactor against all three pre-refactor copies (prod-server Node, deploy.ts worker template, index.ts dev middleware) and the new runPagesRequest owner, plus the new pipeline test suite. Ran tests/pages-request-pipeline.test.ts (34), tests/deploy.test.ts (245), tests/after-deploy.test.ts (9), and tests/pages-router.test.ts (279) — all green.

This is a clean, well-structured consolidation. Verified specifically:

  • Content-type regression fixes (all three) are correct and tested. Render → text/html, API → application/octet-stream, passthrough (middleware short-circuit / external proxy / redirect) → undefined (sent verbatim). The prod adapter's shouldStream || !response.body || defaultContentType === undefined branch matches the pre-refactor render (shouldStream || !body) and API (!body) verbatim-vs-buffer logic exactly. Tests at pipeline lines 181/379/387 cover all three.
  • mergeWebResponsemergeHeaders collapse is behavior-identical. Header precedence (response wins for non-Set-Cookie; Set-Cookie = middleware cookies first, then response cookies), no-body status dropping, and streamed content-length stripping all match the original mergeResponseHeaders + inline logic.
  • Reconciled deltas verified against pre-refactor copies. result.status ?? result.rewriteStatus and preserveCredentialHeaders: isExternalUrl(resolvedUrl) are the correct supersets; the worker previously used url.search for redirect Locations (confirmed in HEAD~11 deploy.ts:711), so omitting rawSearch on the worker path is correct.
  • Dev regression #5 fix is correct. The if (apiMatch) gate around flushStagedHeaders()/flushRequestHeaders() mirrors the original exactly, preserving VINEXT_MW_CTX_HEADER on the app-passthrough next() path.
  • App-passthrough refutations are sound. app-middleware.ts:198 reads the rewrite from the mw-ctx r field (not req.url), so not updating req.url before the dev next() is correct.

One minor edge-case note below (non-blocking). Nice work.

// callback owns the path guards (skip "/", "/api/", and the asset-prefix dir) and
// writes directly to Node `res`, so a `true` result means the response is already sent.
if (deps.serveStaticFile) {
const served = await deps.serveStaticFile(pathname, middlewareHeaders);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor edge-case divergence (non-blocking): serveStaticFile is called with pathname (derived from the inbound request.url at line 194), which for a _next/data request is the normalized page path (the prod adapter rewrites /_next/data/<buildId>/page.json/page before building the webRequest). The pre-refactor prod-server computed staticLookupPath from the raw pathname (HEAD~11 prod-server.ts:1532, before the _next/data normalization at :1627), so it would attempt tryServeStatic("/_next/data/<buildId>/page.json") — effectively always a miss for data requests.

With the new ordering, a _next/data request whose normalized page path happens to collide with an extensionless public file could now serve that public file instead of falling through to the data render. Extremely unlikely in practice (public files normally have extensions; the guard already skips /, /api/, and the asset dir), but it is a behavioral change worth a one-line comment here noting that pathname is post-_next/data-normalization, so a future reader doesn't assume it's the raw inbound path.

@ask-bonk

ask-bonk Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review posted successfully to PR #1860.

Summary

I reviewed this refactor end-to-end against all three pre-refactor copies (prod-server Node, deploy.ts worker template, index.ts dev middleware) and the new runPagesRequest owner, and ran the relevant test suites (34 + 245 + 9 + 279 — all passing).

Overall: solid, well-structured consolidation. All three prior /bigbonk rounds' findings and regressions are properly fixed and tested. I verified the key claims rather than taking them at face value:

  • Content-type regression fixes are correct and tested — render → text/html, API → application/octet-stream, passthrough → verbatim (undefined). The prod adapter's verbatim-vs-buffer branch matches the pre-refactor logic exactly.
  • mergeWebResponsemergeHeaders collapse is behavior-identical — header precedence, Set-Cookie ordering, no-body status dropping, and streamed content-length stripping all match.
  • Reconciled deltas verified against HEAD~11result.status ?? result.rewriteStatus, preserveCredentialHeaders, and the worker's url.search redirect fallback (so omitting rawSearch on the worker is correct).
  • Dev regression fix: inline source content in sourcemaps to prevent missing source warnings #5 fix (if (apiMatch) gate) mirrors the original exactly.
  • App-passthrough refutations are soundapp-middleware.ts:198 reads the rewrite from the mw-ctx r field, not req.url.

One finding posted (minor, non-blocking):

  • _next/data static-file path divergence (pipeline:372) — serveStaticFile now receives the post-_next/data-normalized page path rather than the raw inbound path the pre-refactor prod-server used. Extremely unlikely to matter in practice (public files normally have extensions; the guard skips /, /api/, and the asset dir), but worth a one-line comment noting pathname is post-normalization.

No blocking issues.

github run

@james-elicx james-elicx merged commit bf5b09c into cloudflare:main Jun 9, 2026
41 checks passed
@Divkix Divkix deleted the refactor/pages-request-pipeline branch June 9, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify the triplicated Pages Router request pipeline

2 participants