Skip to content

refactor: extract Pages API route runtime#683

Open
southpolesteve wants to merge 1 commit intomainfrom
codex/pages-api-route-runtime
Open

refactor: extract Pages API route runtime#683
southpolesteve wants to merge 1 commit intomainfrom
codex/pages-api-route-runtime

Conversation

@southpolesteve
Copy link
Collaborator

Summary

  • move the Pages Router API route request/body parsing and Node-style req/res facade into typed server helpers
  • keep pages-server-entry.ts focused on generated wiring by delegating API route execution to handlePagesApiRoute
  • add direct helper coverage for duplicate query/body keys, invalid JSON, oversized bodies, and Buffer responses

Verification

  • vp check packages/vinext/src/server/pages-node-compat.ts packages/vinext/src/server/pages-api-route.ts packages/vinext/src/entries/pages-server-entry.ts tests/pages-api-route.test.ts tests/entry-templates.test.ts
  • vp test run tests/pages-api-route.test.ts tests/entry-templates.test.ts
  • vp test run tests/pages-router.test.ts -t "handles API routes returning JSON|handles dynamic API routes with query params|returns 400 for invalid JSON bodies on Pages API routes|preserves duplicate urlencoded body keys on Pages API routes|sends Buffer payloads from res.send\(\) as raw bytes|preserves invalid JSON failures for Pages API routes in production|preserves duplicate urlencoded body keys for Pages API routes in production|parses empty JSON bodies for Pages API routes in production as \{}|sends Buffer payloads from res.send\(\) as raw bytes in production|preserves content-length for getServerSideProps res.end\(\) short-circuit responses in production"
  • vp run vinext#build

Written by Codex.

@southpolesteve
Copy link
Collaborator Author

/bigbonk

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 25, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@683

commit: 89361bb

@github-actions
Copy link

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Review

Good extraction that aligns with the "generated entries should stay thin" principle from AGENTS.md. The entry now delegates to typed, testable helpers, and the new tests cover the key paths. A few things to address:

Behavioral change: writeHead now lowercases non-set-cookie headers

Old code (in the generated entry):

resHeaders[k] = v;

New code (pages-node-compat.ts:182):

resHeaders[key.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;

The old writeHead stored non-set-cookie header keys as-is (preserving original casing from the caller). The new code lowercases them. This is arguably more correct (and consistent with setHeader which always lowercased), but it is a behavior change. Since end() passes them to new Headers() which is case-insensitive anyway, the practical impact is minimal — but worth noting as intentional.

The old code also stored raw values directly (resHeaders[k] = v), so passing an array for a non-set-cookie header would store the array as-is, which would break new Headers() construction. The new code joins arrays with ", ". This is actually a fix.

Pre-existing bug: setHeader appends to setCookieHeaders instead of replacing

Both old and new code have this issue: setHeader("set-cookie", ...) always appends to setCookieHeaders. In Node.js, res.setHeader() is supposed to replace the previous value for that header name. Calling res.setHeader("set-cookie", "a=1") twice should result in one cookie, not two.

Not introduced by this PR, but worth tracking as a follow-up since this is now the canonical location for this logic.

Fragile error dispatch in pages-api-route.ts:76

if ((error as Error).message === "Request body too large") {
  return new Response("Request body too large", { status: 413 });
}

Matching on error message strings is fragile. parsePagesApiBody already uses PagesApiBodyParseError for structured error dispatch. Consider doing the same for body-too-large — e.g., a typed error class or reusing PagesApiBodyParseError with status 413, thrown from parsePagesApiBody / readPagesRequestBodyWithLimit. This would make the catch clause more robust and avoid accidental matches.

Duplication with api-handler.ts

The dev server (api-handler.ts) has parallel implementations: ApiBodyParseError, getMediaType, isJsonMediaType, parseBody, parseCookies. The new pages-node-compat.ts has equivalent versions for Web Request. They serve different environments (Node.js streams vs Web Request) so they can't be trivially unified, but the media-type helpers and error class could be shared to reduce drift. Not blocking.

Test coverage suggestions

The tests cover the important happy and error paths. A few missing scenarios worth adding given the stated goal of "add direct helper coverage":

  1. match with no default export — the typeof handler !== "function" branch (pages-api-route.ts:51)
  2. match: null — the 404 path
  3. createPagesReqRes directlyres.redirect(), res.setHeader/res.getHeader/res.writeHead, res.send() with object/string data. The Node compat layer is complex enough to warrant direct unit tests.

These are covered by integration tests in pages-router.test.ts, so not blocking, but would strengthen the helper test suite.

Overall

Clean refactor. The API surface is well-typed, the delegation pattern from the generated entry is thin, and the new files are well-structured. The fragile error message matching is the main thing I'd address before merging.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 25, 2026

The review has been posted successfully on PR #683. Here's a summary of the key findings:

Overall: Clean refactor that correctly follows the project's architecture guidelines. The generated entry is now thin, delegating to well-typed helpers in pages-node-compat.ts and pages-api-route.ts.

Key findings:

  1. Fragile error dispatch (main actionable item) — pages-api-route.ts:76 matches on error.message === "Request body too large" instead of using a typed error class. This should use structured error dispatch like the existing PagesApiBodyParseError.

  2. Intentional behavioral improvementwriteHead now lowercases non-set-cookie header keys and joins array values with ", ", which fixes a latent bug in the old code.

  3. Pre-existing bug notedsetHeader("set-cookie", ...) appends instead of replacing, which differs from Node.js semantics. Not introduced by this PR.

  4. Duplication with api-handler.ts — media-type helpers and error classes could be shared to reduce drift. Not blocking.

  5. Test coverage suggestions — missing direct tests for the 404 path, missing-export path, and createPagesReqRes methods (redirect, setHeader, getHeader, writeHead).

github run

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.

1 participant