Skip to content

fix(config): match trailing-slash route sources#2255

Open
james-elicx wants to merge 2 commits into
mainfrom
codex/fix-trailingslash-isr-gsp
Open

fix(config): match trailing-slash route sources#2255
james-elicx wants to merge 2 commits into
mainfrom
codex/fix-trailingslash-isr-gsp

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • match config rewrites, redirects, and headers when sources end in /
  • preserve Next.js optional trailing-slash matching after canonical request normalization
  • restore generated-static-param ISR routes reached through trailing-slash rewrites

Next.js parity

Targets two failures in the trailing-slash ISR/generateStaticParams deploy-suite cluster. The prerendered destination was correct; the canonical rewritten source failed before ISR assertions because its config source no longer matched.

Validation

  • targeted production fixture: rewritten source renders the generated static route
  • 16 focused matcher assertions
  • scoped format, lint, and type checks
  • vp run vinext#build
  • independent review: no actionable findings

Full suites are deferred to CI.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: bf41b75

@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

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@github-actions

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared bf41b75 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.45 s 2.43 s ⚫ -0.7%
Production build time vinext 3.05 s 3.04 s ⚫ -0.3%
RSC entry closure size (gzip) vinext 92.2 KB 92.3 KB ⚫ +0.1%
Server bundle size (gzip) vinext 166.9 KB 167.0 KB ⚫ +0.0%

View detailed results and traces

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

@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: trailing-slash config route source matching

I verified the core behavioral change against the authoritative Next.js implementation and ran the matchers across a broad parity matrix. The fix is correct and matches Next.js behavior. No actionable issues found.

Verification against Next.js

Next.js compiles config route sources (rewrites/redirects/headers) with path-to-regexp in non-strict mode (getPathMatch in shared/lib/router/utils/path-match.ts, default strict: false). I compiled the vendored Next.js path-to-regexp (.nextjs-ref/.../compiled/path-to-regexp) to derive the ground-truth match table:

source matches /x matches /x/
/x
/x/
/:l(en|es)
/:l(en|es)/
/api/:path*/ ❌ (/api) ✅ (/api/)

Non-strict path-to-regexp appends an optional trailing-slash matcher, so a slash-ending source matches the slashed canonical form but not the slash-free form. The PR's new test assertions (matchConfigPattern("/en/", "/:lang(en|es)/") ⇒ match, "/en" ⇒ null; same for matchHeaders /docs/) line up exactly with this table.

I ran matchConfigPattern against a 13×13 pattern/pathname matrix (170 cases) cross-checked against the vendored path-to-regexpall 170 matched Next.js exactly, including the catch-all edge cases (:path* zero-segment, :path+ minimum-segment) that the removed comment was guarding against. Param extraction values are also correct for slash-ending patterns.

Implementation notes (all sound)

  • matchConfigPattern: stripping the trailing slash from the pattern only when the pathname had one correctly reproduces non-strict optional-slash semantics without changing catch-all/root behavior. Root / is preserved (length > 1 guard).
  • matchRedirect: the locale-static fast path uses normalizedPathname for Map.get keys (derived from slash-free sources), while the linear fallback passes the original pathname into matchConfigPattern. Verified _LOCALE_STATIC_RE requires a non-empty suffix, so a slash-only source like /:lang(en|es)/ falls to the linear path — consistent with the new test.
  • matchHeaders: the compiled-regex cache key now matches the (possibly-stripped) source used to build the regex, so there is no cache poisoning. Cache growth is at most 2× a finite static rule set — negligible.
  • All callers (matchRewrite, matchesRewriteSource, request pipelines) pass the unstripped pathname, which is what the fix expects.

Tests run (all green)

  • New tests/app-trailing-slash-isr.test.ts integration test (rewrite + ISR/GSP end-to-end) — passes
  • tests/shims.test.ts matchConfigPattern / matchRedirect / matchHeaders suites — pass
  • tests/routing.test.ts, tests/route-sorting.test.ts, tests/isr-cache.test.ts — pass
  • 170-case + param-extraction parity checks vs vendored path-to-regexp — pass

LGTM.

@ask-bonk

ask-bonk Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2255.

Summary

I reviewed PR #2255 (fix(config): match trailing-slash route sources) and found no actionable issues — the fix is correct and matches Next.js behavior.

What the PR does

Makes matchConfigPattern, matchRedirect, and matchHeaders correctly match config route sources (rewrites/redirects/headers) that end in /, so trailing-slash ISR + generateStaticParams rewrites resolve from their canonical slashed URLs.

Verification approach

I cloned the Next.js reference repo and confirmed that Next.js compiles config sources with path-to-regexp in non-strict mode (getPathMatch, default strict: false), which appends an optional trailing-slash matcher. I compiled the vendored path-to-regexp to build a ground-truth match table, then:

  • Cross-checked matchConfigPattern against a 170-case pattern/pathname matrix — all matched Next.js exactly, including the catch-all edge cases the removed comment worried about.
  • Verified param extraction values for slash-ending patterns.
  • Confirmed the locale-static index correctly routes slash-only sources to the linear fallback (via _LOCALE_STATIC_RE requiring a non-empty suffix).
  • Confirmed the matchHeaders regex cache key stays consistent with the compiled source (no cache poisoning).
  • Ran the new integration test plus shims, routing, route-sorting, and isr-cache suites — all green.

The PR's new test assertions align exactly with verified Next.js semantics. LGTM.

github run

@james-elicx james-elicx marked this pull request as ready for review June 22, 2026 00:24
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