Skip to content

fix(pages): match optional catch-all prerender paths - #2238

Merged
james-elicx merged 3 commits into
mainfrom
codex/prerender-parity-27858251903
Jun 23, 2026
Merged

james-elicx merged 3 commits into
mainfrom
codex/prerender-parity-27858251903

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • normalize optional catch-all getStaticPaths empty values like Next.js
  • validate all required dynamic params for mixed routes
  • share the route-aware matcher across development and production

Validation

  • 53 focused page-data unit tests
  • 2 focused dev integration tests
  • 1 focused production Playwright test
  • scoped vp check
  • independent cumulative review: clean

Next.js parity

Targets the optional catch-all prerender failure from test/e2e/prerender.test.ts in deploy-suite run 27858251903. Verified against Next.js v16.2.6 static-path normalization. No raw Next.js E2E was run locally.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jun 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 73e6ee8

@github-actions

Copy link
Copy Markdown
Contributor
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
workers-cache preview production
web preview production

@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.

Review: fix(pages): match optional catch-all prerender paths

I verified the change against the authoritative Next.js source (.nextjs-ref/packages/next/src/build/static-paths/pages.ts, v16 canary) and ran the focused suites. The implementation is correct and faithfully mirrors Next.js. No blocking issues.

What I checked

Parity with Next.js buildPagesStaticPaths — the new matchesPagesStaticPath reproduces the upstream per-param validation loop closely:

  • Iterates routeParameterKeys (here getPagesRouteParams derived from the bracket pattern), not the raw entry keys — matches Next.js, so extra entry keys are correctly ignored.
  • Optional catch-all normalization of null | undefined | false → [] matches Next.js (paramValue = []).
  • Required catch-all with a non-array (or empty) value is rejected — matches the Next.js repeat && !Array.isArray(paramValue) throw. The "rejects required catch-all" it.each covers all four cases.
  • Single dynamic params require typeof value === "string" — matches Next.js. (vinext returns 404 instead of throwing at build time; this is the pre-existing, documented behavioral choice noted in the JSDoc, and is fine.)

Object.hasOwn gate — Next.js only normalizes optional [] when params.hasOwnProperty(key) is true; an entirely-omitted key falls through to the typeof paramValue === 'undefined' throw. The PR's !Object.hasOwn(entryParams, key) → return false correctly reproduces this (404 instead of throw).

Runtime param shape — confirmed via route-trie.ts:163-169 that an empty optional catch-all does not materialize the param (params.slug is undefined, not []). The optional && value.length === 0 && actual === undefined → true branch is exactly what handles this, and the empty-array fallthrough also resolves correctly for the non-empty cases.

Dev/prod parity — both paths feed the same Next.js bracket pattern into getPagesRouteParams (patternToNextFormat(route.pattern) in dev-server.ts and pages-page-handler.ts:418; options.routePattern in resolvePagesPageData). Sharing the matcher removes the prior dev-only divergence. No dead imports left behind in dev-server.ts.

Regex ordering in getPagesRouteParams — optional [[...x]] is matched before required [...x] before dynamic [x], so greedier patterns can't shadow more specific ones. Correct.

Verification run

  • tests/pages-page-data.test.ts — 53/53 pass
  • tests/pages-router.test.ts -t "optional catch-all" + mixed-catchall dev tests — pass (/catchall-optional → 200, /mixed-catchall/guides → 200, /mixed-catchall/unlisted → 404)
  • vp check on both changed source files — clean (format, lint, types)

Minor nit (non-blocking)

In pages-page-data.ts, the JSDoc block describing matchesPagesStaticPath (lines 443-457) now sits directly above the newly-inserted PagesRouteParam type and getPagesRouteParams function, leaving matchesPagesStaticPath itself (line 485) undocumented. Consider moving that JSDoc back down to matchesPagesStaticPath and giving getPagesRouteParams its own short comment.

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2238.

Summary

I reviewed the change against the authoritative Next.js source and ran the focused test suites. The implementation is correct — no blocking issues.

Key findings (all verified clean):

  • matchesPagesStaticPath faithfully mirrors Next.js buildPagesStaticPaths: optional null|undefined|false → [] normalization, required catch-all rejection, single-param string requirement, and the Object.hasOwn gate all match upstream behavior.
  • Confirmed via route-trie.ts that empty optional catch-all leaves the param undefined, which the optional && value.length === 0 && actual === undefined branch handles correctly.
  • Dev and prod now share the same matcher fed identical Next.js bracket patterns — removes the prior dev-only divergence.
  • Regex ordering in getPagesRouteParams correctly prioritizes optional > required > dynamic.
  • No dead imports left behind.

Verification: 53/53 page-data unit tests pass, dev integration tests pass (root → 200, mixed → 200, unlisted → 404), vp check clean.

One minor non-blocking nit: the JSDoc for matchesPagesStaticPath now sits above the newly-inserted getPagesRouteParams/PagesRouteParam, leaving matchesPagesStaticPath undocumented — worth relocating.

Overall: LGTM.

github run

@github-actions

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 73e6ee8 against base cf9d601 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 132.8 KB 132.8 KB ⚫ 0.0%
Client entry size (gzip) vinext 118.7 KB 118.7 KB ⚫ 0.0%
Dev server cold start vinext 2.40 s 2.41 s ⚫ +0.7%
Production build time vinext 2.92 s 2.96 s ⚫ +1.4%
RSC entry closure size (gzip) vinext 92.2 KB 92.2 KB ⚫ -0.0%
Server bundle size (gzip) vinext 166.9 KB 166.9 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx
james-elicx marked this pull request as ready for review June 21, 2026 19:42
@james-elicx
james-elicx merged commit 640e971 into main Jun 23, 2026
58 checks passed
@james-elicx
james-elicx deleted the codex/prerender-parity-27858251903 branch June 23, 2026 19:00
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