Commit Graph

6 Commits

Author SHA1 Message Date
Nathan Nguyen 3253aafd77 fix(pages-router): pass rewrite URL to edge API requests (#1998)
* fix(pages-router): pass rewrite URL to edge API requests

* fix(pages-router): preserve edge API request URL parity

* fix(pages-router): configure edge API NextRequest

* fix(pages-router): preserve edge API locale config

* fix(pages-router): preserve edge request metadata

* fix(pages-router): align edge API URL formatting

* fix(pages-router): match API locale casing

* fix(pages-router): match domain locales by locale

* docs(pages-router): clarify edge API basePath parity

* test(deploy): expect metadata-preserving URL clone

* docs(pages-router): note basePath rewrite parity

---------

Co-authored-by: James <james@eli.cx>
2026-06-13 22:05:31 +01:00
Nathan Nguyen 2737a38d46 fix(pages-router): support stream proxying in API routes (#1902)
* fix(pages-router): support stream proxying in API routes

* fix(pages-router): address review feedback on stream proxying PR

- Narrow auto-end condition to only skip ending when handler returns res
  (the destination of req.pipe(...).pipe(res)), preventing hangs from
  non-stream return values.
- Replace full-body buffering with a PassThrough-backed PagesResponseStream
  that resolves on first write, enabling real streaming responses.
- Add error handling to reject responsePromise when the response stream
  errors, preventing silent request hangs.
- Add regression tests: non-stream return auto-end, multi-chunk write,
  and response stream error handling.

* fix(pages-router): avoid hanging responses and PassThrough unhandled errors

- Narrow auto-end condition to  to prevent non-stream
  return values from hanging requests.
- Replace PassThrough + Readable.toWeb() with a direct ReadableStream controller
  in PagesResponseStream to eliminate Controller is already closed unhandled
  errors during stream proxying.
- Add destroy() override to error/close the stream body when the Node Writable
  is destroyed after streaming starts.
- Add rejectResponse callback to PagesResponseStream constructor for proper
  error propagation.
- Add regression tests for auto-end, multi-chunk streaming, and destroy error
  handling.

* fix(pages-router): destroy Node side when Fetch body is cancelled

- Reject writes in _write() when streamEnded or destroyed to prevent
  buffering chunks after cancellation.
- In the ReadableStream cancel handler, clear bufferedChunks and destroy
  the Node-compatible PagesResponseStream so upstream writers receive
  an error instead of silently accumulating data.
- Add regression test proving writes after body cancellation fail with
  an error and do not accumulate in the bridge.
- Rename multi-chunk test to be more accurate about what it verifies.

* fix(pages-router): settle response when res.destroy() is called without error

- In destroy(), if the response has not been resolved yet, resolve the
  response promise (with a closed stream) when no error is provided, or
  reject it when an error is provided. This prevents the request from
  hanging forever when a handler calls res.destroy() before any body
  write and returns the res object.
- Add regression test proving res.destroy() without error resolves to an
  empty 200 response instead of hanging.

* chore: trigger CI run for flaky E2E test validation

* fix(pages-router): track pipe event and fix request body cancellation

- Add `res.once("pipe", ...)` listener to detect when a handler pipes into the response without returning the writable. Skipping auto-end by return-value only missed the real-world pattern:

    (req, res) => { req.pipe(upstream).pipe(res); } // returns undefined

  Next.js uses the pipe event signal, not the handler return value.

- Fix request body cancellation: the async generator finally block now calls `reader.cancel()` before `releaseLock()`, restoring the pre-PR behavior where early return from `for await (chunk of req)` cancelled the underlying stream instead of just releasing the lock.

- Add regression tests for both pipe-without-return and pipe-with-return.

* fix(pages-router): drop handlerResult === res auto-end signal, rely on pipe event only

The `handlerResult === res` check was too broad — chainable helpers like
`return res.status(202)` return `this` without implying streaming, and would
cause a hang by skipping auto-end. Now that `res.once("pipe", ...)` provides
the real stream signal, the return-value check is redundant and harmful.

Removes:
- handlerResult variable and === res comparison
- comment about returning the writable

Adds:
- regression test for `return res.status(202)` not hanging

* fix(pages-router): address stream response review

* fix(pages-router): close IncomingMessage parity gaps in API route streams

- Yield Buffer chunks (zero-copy views) from the request readable and opt
  out of objectMode so req matches IncomingMessage byte-stream semantics:
  per-chunk chunk.toString("utf8") decodes instead of comma-joining byte
  values, and setEncoding()/read(n) behave byte-wise.
- Honour config.api.externalResolver: Next.js never auto-ends a response,
  and externalResolver: true is the documented contract for handlers that
  respond after the handler promise settles (e.g. proxy middleware that
  attaches its pipe asynchronously). Skip the auto-end safety net for such
  routes so they are not truncated to an empty response.

---------

Co-authored-by: James <james@eli.cx>
2026-06-11 23:29:58 +00:00
James Anderson b8617a4c40 fix(pages-router): wrap edge API request in NextRequest, support bare runtime export (#1391)
Two parity gaps with Next.js were causing edge runtime API routes to fail
with 500 errors:

1. Edge API handlers received a plain `Request` object, so accessing
   `req.nextUrl.searchParams` (the idiomatic pattern in Next.js edge API
   routes) threw at runtime. Next.js wraps the request in `NextRequest`
   before invoking the handler (NextRequestHint in
   next/src/server/web/adapter.ts).

2. Only `export const config = { runtime: 'edge' }` was recognised as an
   edge runtime declaration. Next.js also accepts a bare
   `export const runtime = 'edge'` and resolves the effective runtime as
   `config.runtime ?? config.config?.runtime`
   (next/src/build/analysis/get-page-static-info.ts).

Both fixes are applied in dev (`server/api-handler.ts`) and prod
(`server/pages-api-route.ts`) so the two paths stay in parity.

Refs #1338
2026-05-21 10:32:07 +01:00
Nathan Nguyen 3262443f0b fix(pages-api): execute edge API routes with Fetch Request (#1320)
* fix(pages-api): execute edge API routes with Fetch Request

Pages API routes with config.runtime set to edge were still executed through the Node-style req/res adapter. That gives user handlers a plain headers object and turns valid Edge API code that returns a Response into a 500.

Detect edge and experimental-edge API route modules before Node body parsing, pass the Fetch Request through, and forward the returned Response. The dev Node bridge mirrors the generated Web Request path and preserves Set-Cookie response headers.

Regression coverage ports the relevant Next.js edge-async-local-storage contract for Fetch Request execution, concurrent AsyncLocalStorage isolation, and the dev bridge.

* fix(pages-api): stream edge API responses in dev

* fix(pages-api): handle closed dev edge streams

* fix(pages-api): stream dev edge request bodies

* fix(pages-api): validate dev edge forwarded protocol
2026-05-19 18:12:42 +01:00
Steve Faulkner 0e62dc24c7 Prefer Pages Router route params over same-key query params (#1119) 2026-05-06 15:01:39 -05:00
Steve Faulkner da04d2062b refactor: extract Pages API route runtime (#683)
* refactor: extract pages api route runtime

* refactor: share Pages ISR cache runtime (#684)

* fix: address Pages API route review feedback

- Replace fragile error.message string matches with typed PagesBodyParseError
  (statusCode: 413) thrown from pages-node-compat.ts and api-handler.ts; the
  existing instanceof catch in handlePagesApiRoute handles both 400 and 413
- Fix res.setHeader('set-cookie') to replace instead of append on repeated
  calls, matching Node.js ServerResponse semantics
- Extract shared getMediaType/isJsonMediaType helpers and the body-parse error
  class into pages-media-type.ts to eliminate drift between api-handler.ts and
  pages-node-compat.ts
- Add unit tests for 404 (null match), missing default export (500), redirect
  (1-arg and 2-arg), writeHead header lowercasing/array joining, setHeader/
  getHeader round-trip, and set-cookie replace-not-append parity

---------

Co-authored-by: James <james@eli.cx>
2026-03-28 09:36:30 +00:00