[PR #401] [MERGED] fix: Reject non-terminal catch-all routes in pages and app routers #544

Closed
opened 2026-05-06 13:08:40 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/401
Author: @JaredStowell
Created: 3/10/2026
Status: Merged
Merged: 3/10/2026
Merged by: @james-elicx

Base: mainHead: jstowell/fix-nonterminal-catchall-routes


📝 Commits (8)

  • ce6f6a4 Fix catch-all route overmatching
  • 7cfb368 Fix non-terminal slot routes
  • f660f15 Fix malformed slot route discovery
  • 11ad0a1 Add validation for intercept catch-⁤
  • e726bdf Fix intercepting route catch-alls
  • 52a9d6d bonk
  • 704bcc2 Fix routing test route literals for typecheck
  • be71f21 Fix app route matcher test fixtures

📊 Changes

7 files changed (+484 additions, -100 deletions)

View changed files

📝 packages/vinext/src/entries/app-rsc-entry.ts (+2 -0)
📝 packages/vinext/src/entries/pages-server-entry.ts (+2 -0)
📝 packages/vinext/src/routing/app-router.ts (+63 -92)
📝 packages/vinext/src/routing/pages-router.ts (+18 -7)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+14 -0)
📝 tests/entry-templates.test.ts (+5 -0)
📝 tests/routing.test.ts (+380 -1)

📄 Description

Fixes a routing bug where non-terminal catch-all segments were accepted during route discovery and then overmatched at runtime.

Before this change, paths like pages/[...slug]/edit/index.tsx were converted into the invalid internal pattern /:slug+/edit, and the matcher would return as soon as it saw :slug+ or :slug*. That caused the route to match URLs it should never match, including both /foo and /foo/edit.

This change fixes that in both the Pages Router and App Router.

What changed

  • Reject non-terminal catch-all segments during Pages Router route discovery
  • Reject non-terminal catch-all segments during App Router route discovery
  • Add defensive matcher guards so malformed internal patterns like /:slug+/edit and /:slug*/edit do not overmatch
  • Preserve valid App Router behavior when only transparent segments like route groups follow on disk
  • Extend routing tests to cover invalid discovery, malformed matcher inputs, API routes, fallback behavior, and route-group edge cases

Details

Pages Router

  • fileToRoute() now rejects [...param] and [[...param]] unless they are terminal
  • matchPattern() now treats non-terminal :param+ and :param* as invalid instead of returning early

App Router

  • fileToAppRoute() now rejects non-terminal catch-all segments based on visible URL segments, not raw filesystem depth
  • Route groups and slots remain transparent, so cases like app/[...slug]/(admin)/page.tsx still resolve to valid /:slug+
  • matchPattern() now rejects malformed non-terminal internal catch-all patterns

Tests

Added regression coverage for:

  • Pages non-terminal catch-all discovery rejection
  • Pages non-terminal optional catch-all discovery rejection
  • Pages matcher rejection of malformed /:slug+/edit
  • Pages matcher rejection of malformed /:slug*/edit
  • Pages fallback to later valid routes after malformed entries
  • API router rejection of invalid non-terminal catch-all routes
  • App non-terminal catch-all page rejection
  • App non-terminal optional catch-all route-handler rejection
  • App non-terminal catch-all rejection when static suffix is behind a route group
  • App acceptance of terminal catch-all routes followed only by transparent route groups
  • App matcher rejection of malformed internal catch-all patterns
  • App fallback to later valid routes after malformed entries

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/401 **Author:** [@JaredStowell](https://github.com/JaredStowell) **Created:** 3/10/2026 **Status:** ✅ Merged **Merged:** 3/10/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `jstowell/fix-nonterminal-catchall-routes` --- ### 📝 Commits (8) - [`ce6f6a4`](https://github.com/cloudflare/vinext/commit/ce6f6a46eba3e21a902b41e9344f87966506bbb8) Fix catch-all route overmatching - [`7cfb368`](https://github.com/cloudflare/vinext/commit/7cfb368b2fb3329edbe70200cfed322c4340f71b) Fix non-terminal slot routes - [`f660f15`](https://github.com/cloudflare/vinext/commit/f660f1515ad8bcf3a0ba124823e911c8900ffa0f) Fix malformed slot route discovery - [`11ad0a1`](https://github.com/cloudflare/vinext/commit/11ad0a16fea1ce6679e4180f612c08aae11ecb2c) Add validation for intercept catch-⁤ - [`e726bdf`](https://github.com/cloudflare/vinext/commit/e726bdf2df2303e0256cebbb19515c2155f05014) Fix intercepting route catch-alls - [`52a9d6d`](https://github.com/cloudflare/vinext/commit/52a9d6def35fa05092e77380ac9b9eb631696a0f) bonk - [`704bcc2`](https://github.com/cloudflare/vinext/commit/704bcc2ba371bdd8dcbd89f69fbdc9471780db99) Fix routing test route literals for typecheck - [`be71f21`](https://github.com/cloudflare/vinext/commit/be71f21cbe21e8b424b174059eb150eb25be79a3) Fix app route matcher test fixtures ### 📊 Changes **7 files changed** (+484 additions, -100 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+2 -0) 📝 `packages/vinext/src/entries/pages-server-entry.ts` (+2 -0) 📝 `packages/vinext/src/routing/app-router.ts` (+63 -92) 📝 `packages/vinext/src/routing/pages-router.ts` (+18 -7) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+14 -0) 📝 `tests/entry-templates.test.ts` (+5 -0) 📝 `tests/routing.test.ts` (+380 -1) </details> ### 📄 Description Fixes a routing bug where non-terminal catch-all segments were accepted during route discovery and then overmatched at runtime. Before this change, paths like `pages/[...slug]/edit/index.tsx` were converted into the invalid internal pattern `/:slug+/edit`, and the matcher would return as soon as it saw `:slug+` or `:slug*`. That caused the route to match URLs it should never match, including both `/foo` and `/foo/edit`. This change fixes that in both the Pages Router and App Router. ## What changed - Reject non-terminal catch-all segments during Pages Router route discovery - Reject non-terminal catch-all segments during App Router route discovery - Add defensive matcher guards so malformed internal patterns like `/:slug+/edit` and `/:slug*/edit` do not overmatch - Preserve valid App Router behavior when only transparent segments like route groups follow on disk - Extend routing tests to cover invalid discovery, malformed matcher inputs, API routes, fallback behavior, and route-group edge cases ## Details ### Pages Router - `fileToRoute()` now rejects `[...param]` and `[[...param]]` unless they are terminal - `matchPattern()` now treats non-terminal `:param+` and `:param*` as invalid instead of returning early ### App Router - `fileToAppRoute()` now rejects non-terminal catch-all segments based on visible URL segments, not raw filesystem depth - Route groups and slots remain transparent, so cases like `app/[...slug]/(admin)/page.tsx` still resolve to valid `/:slug+` - `matchPattern()` now rejects malformed non-terminal internal catch-all patterns ## Tests Added regression coverage for: - Pages non-terminal catch-all discovery rejection - Pages non-terminal optional catch-all discovery rejection - Pages matcher rejection of malformed `/:slug+/edit` - Pages matcher rejection of malformed `/:slug*/edit` - Pages fallback to later valid routes after malformed entries - API router rejection of invalid non-terminal catch-all routes - App non-terminal catch-all page rejection - App non-terminal optional catch-all route-handler rejection - App non-terminal catch-all rejection when static suffix is behind a route group - App acceptance of terminal catch-all routes followed only by transparent route groups - App matcher rejection of malformed internal catch-all patterns - App fallback to later valid routes after malformed entries --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:40 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#544
No description provided.