Commit Graph

18 Commits

Author SHA1 Message Date
James Anderson a09207618a fix(pages): normalize decoded edge responses (#2668)
* fix(pages): normalize decoded edge responses

* fix(pages): thread hybrid edge runtime
2026-07-22 17:56:12 +01:00
James Anderson 71dbb45ca8 fix(pages): isolate on-demand revalidation requests (#2495)
* fix(pages): pin revalidate loopback origin

* test(pages): cover production revalidation origin

* fix(pages): preserve internal revalidation boundaries

* fix(pages): preserve on-demand revalidation boundaries

* fix(pages): dispatch Worker revalidation internally

* fix(pages): authenticate revalidation request context

* fix(pages): isolate revalidation transport headers

* test(pages): account for generated route table size

* fix(pages): align on-demand revalidation semantics

* fix(pages): align revalidation response cache parity

* fix(pages): preserve regenerated ISR representations

* fix(pages): align ISR cache representations

* fix(pages): preserve canonical ISR representations

* fix(pages): align cached response parity

* fix(pages): preserve custom App render props

* fix(pages): preserve optional App page props

* fix(pages): preserve optional App props on the client

* fix(pages): normalize client App page props

* fix(pages): preserve App data merge semantics

* fix(pages): keep redirect status helper internal

* fix(pages): match Next.js terminal ISR behavior

* fix(pages): preserve custom app error envelopes

* fix(pages): match Next.js dev revalidation semantics

* test(pages): align dev revalidation parity coverage

* chore(pages): remove stale cache helper exports

* fix(cache): preserve explicit no-store context
2026-07-20 18:45:06 +01:00
James Anderson 555481714c fix(pages): align preview mode behavior (#2561)
* fix(pages): align preview mode behavior

* fix(pages): secure preview mode boundaries

* fix(pages): isolate preview credentials

* fix(pages): unify draft mode credentials

* fix(build): isolate preview credentials per build

* fix(api): align pages draft mode helpers

* fix(pages): address preview review findings

* fix(pages): align preview request types

* fix(pages): export preview data type

* fix(types): align pages API declarations

* fix(types): preserve NextApiRequest augmentation

* fix(pages): align API redirect responses

* fix(types): align API request env runtime

* fix(types): preserve API request env parity

* fix(pages): align dev preview fallback behavior

* fix(pages): scope preview state to data pages

* fix(pages): preserve cookies when clearing preview

* fix(pages): scope preview clear cookie dedupe
2026-07-09 14:41:49 +00:00
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
James Anderson 24d3dfcf00 fix(pages): harden cookie parsing (#1947)
* fix(pages): harden cookie parsing

* fix(pages): preserve cookie object compatibility

* fix(shims): distinguish edge cookie parser

* refactor(cookies): dedupe header parsing
2026-06-13 00:57:45 +01:00
James Anderson 1f5fb6b42c fix(security): gate x-forwarded-proto in edge API runtime on trustProxy (F-PROD-7) (#1618)
The dev edge API bridge (`createEdgeApiRequest` in `api-handler.ts`) was
reading `X-Forwarded-Proto` without the `trustProxy` gate that the rest of
the prod server uses. A client that can reach the dev server directly
could send `X-Forwarded-Proto: https` and trick edge handlers that gate
Secure-cookie issuance on `request.url.startsWith("https")` (or any other
`request.url.protocol` check) into believing the request was
TLS-terminated.

Same issue applied to `X-Forwarded-Host` in the same function: the raw
header value was used to build the request URL, opening a host-header
poisoning vector identical to the one `prod-server.resolveHost` already
guards against.

This commit:

* Extracts `resolveRequestProtocol`, `resolveRequestHost`, `trustProxy`,
  and `trustedHosts` into a new shared module
  `packages/vinext/src/server/proxy-trust.ts`. The helpers accept both
  Node `IncomingMessage` and Web `Headers` so the same trust policy
  applies in every server flavor.
* Updates `prod-server.ts` to delegate to the shared module
  (re-exporting `resolveHost`, `trustedHosts`, and `trustProxy` to keep
  the existing public surface and the tests that mutate `trustedHosts`
  working).
* Updates `createEdgeApiRequest` to use the new helpers so dev edge API
  routes honour `X-Forwarded-Proto` / `X-Forwarded-Host` only when
  `VINEXT_TRUST_PROXY=1` / `VINEXT_TRUSTED_HOSTS` is configured.

Tests:

* New `tests/api-handler-trust-proxy.test.ts` covers the default
  (untrusted) behaviour, `VINEXT_TRUST_PROXY=1`, and the
  `VINEXT_TRUSTED_HOSTS` allow-list (including the implicit
  `trustProxy` enablement, case-insensitive matching, and
  comma-separated values).
* Updated the existing "uses the first x-forwarded-proto value" test to
  reflect the new (correct) default of ignoring forged proxy headers.

Reference: Finding F-PROD-7 in SECURITY-AUDIT-2026-05.md.
2026-05-27 16:17:16 +01: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
James Anderson 1ff166eb92 chore: turn on more lint rules (#714)
* chore: turn on more lint rules

* fmt

* add todos
2026-03-29 20:40:57 +01:00
James Anderson 9cec92beaa refactor: migrate ssrLoadModule to moduleRunner.import (#570)
* refactor: migrate ssrLoadModule to moduleRunner.import

Replace all server.ssrLoadModule() calls in dev-server.ts and api-handler.ts
with runner.import() via the ModuleImporter interface, following the Vite
Module Runner migration guide. Remove ssrFixStacktrace calls which are not
needed with Module Runner APIs. Update call sites in index.ts to pass the
existing getPagesRunner() lazy factory as the runner argument. Update tests
to use ModuleImporter-shaped mocks instead of the old ViteDevServer mock.

* refactor: address bonk review comments

- Fix misleading stack trace comments: ssrFixStacktrace is not applicable
  to ModuleRunner (not that it rewrites automatically)
- Update ALS-ARCHITECTURE.md to reference runner.import() instead of
  server.ssrLoadModule()
- Add importModule() typed helper to instrumentation.ts to centralise
  the Record<string, any> cast, removing 12 inline eslint-disable comments
- Remove dead ssrLoadModule mock from pages-router ISR test (createSSRHandler
  no longer calls server.ssrLoadModule)
2026-03-17 14:23:20 +00:00
Stephen Zhou 25f6e0fc87 chore: enable typeAware and typeCheck, use vp check (#551)
* chore: enable typeAware and typeCheck, use vp check

* Build

* Try cache false

* Revert "Try cache false"

This reverts commit 5f76ed02f1.

* Update

* workaround for vp check

* Try no workaround

* Fix check

* Update to 0.1.12

* denyWarnings
2026-03-16 11:00:47 -05:00
Stephen Zhou 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 009da10473.

* Update

* Update

* Try revert ci changes

* revert

* Run vp migrate

* Disable typeAware and typeCheck for now

* Better resolve for test

* Use vp dev instead of vite

* Update expect

* Fix NormalizeManifestModuleId

* Try increase timeout

* Update to use vp

* Try new check

* Bring back npx vp

* Migrate CI

* Make next-intl resolvable

* Update

* Update

* Update
2026-03-15 10:50:13 +00:00
Jared Stowell f360380112 fix: Pages API body parser for invalid JSON and repeated form keys (#446)
* Fix Pages API parsing parity

* Fix Pages API body parsing parity

* Fix empty form body parity

* Regenerate entry-templates snapshots after merge

* fix: propagate statusText through prod server sendCompressed non-compressed path

The sendCompressed function had a statusText parameter but only used it in
the compressed response path. The non-compressed else branch called
res.writeHead directly without forwarding statusText, so short error
responses (like 'Invalid JSON' at 12 bytes, well below COMPRESS_THRESHOLD)
lost their custom reason phrase and fell back to the default 'Bad Request'.

Fix: replace the direct res.writeHead call in the else branch with the
writeHead closure that already handles the statusText conditional.

Also set statusText on the ApiBodyParseError response in the pages server
entry template so the value is present for prod-server to forward.

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 13:24:36 +00:00
Jared Stowell 3b982066d0 fix: align Pages API body parsing and res.send(Buffer) (#428)
* Fix Buffer handling in Pages API res

* fix: skip reporting handled Pages API parse errors

* address review: blank line nit, content-length parity for Buffer, fix duplicate Content-Length in sendCompressed

* fix: set content-length for Buffer in production res.send() to match dev parity

---------

Co-authored-by: James <james@eli.cx>
2026-03-11 11:16:51 +00:00
Jared Stowell ec1b9da131 fix: preserve Pages Router query arrays and hash in asPath (#421)
* Handle array query params

* Fix router query merge order
2026-03-10 22:17:36 +00:00
Nathan Nguyen 0b63ad5237 perf: pre-split route patterns and hoist URL split out of match loop (#396)
* perf: pre-split route patterns and hoist URL split out of match loop

Route matching runs on every request and iterates all routes until a
match is found. Previously, matchPattern() re-split both the URL and
the pattern string on every iteration:

  for (const route of routes) {
    // url.split("/") — same URL, re-split every iteration
    // pattern.split("/") — pattern never pre-computed
    matchPattern(normalizedUrl, route.pattern);
  }

For a 50-route app on a 404, that's 50 URL splits + 50 pattern splits
+ 50 Object.create(null) + 100 .filter(Boolean) calls.

Fix:
- Add `patternParts: string[]` to Route and AppRoute interfaces,
  computed once at scan time
- Split the URL once in matchRoute/matchAppRoute before the loop
- Pass pre-split arrays to matchPattern instead of raw strings
- Apply the same optimization to generated production entries
  (pages-server-entry.ts, app-rsc-entry.ts)
- Fix intercepting route lookup to use the new array-based API

* update entry-templates snapshots

---------

Co-authored-by: James <james@eli.cx>
2026-03-10 08:18:30 +00:00
James Anderson 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
2026-03-09 14:56:14 +00:00
Nathan Nguyen c1ceef2aaf test: add unit tests for Pages Router api-handler (#318)
Cover body parsing (JSON, form-urlencoded, plain text, empty, malformed),
cookie parsing (single, multiple, values with =, missing header),
req/res extensions (status chaining, json, send for objects/strings/numbers/null,
redirect with default 307 and custom status codes), query string and dynamic
param merging (including array promotion for duplicate keys), MAX_BODY_SIZE
enforcement (413 for >1 MB), and error handling (missing default export,
non-function export, handler throws, ssrFixStacktrace integration).
2026-03-07 17:11:56 +00:00