Skip to content

fix(pages): report page route clashes and match encoded page paths - #545

Merged
Brooooooklyn merged 1 commit into
mainfrom
fix/pages-route-clashes
Oct 22, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
fix/pages-route-clashes

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Follow-up to #534. Fixes page routing bugs that were already on main.

What was broken

Case Before
about.tsx + about/index.tsx, x.tsx + x.vue, (a)/team.tsx + (b)/team.tsx Two files for one route, no warning. The server and the client could each pick a different file (one file's loader fed the other file's component). Route groups changed winner between runs. With two loaders, dev failed with Identifier '_pload_about' has already been declared.
index.tsx + index/index.tsx Different routes, same page id index, so one route rendered the other page.
layout/index.tsx next to layout.tsx The page had the layout's id, so /layout rendered the layout as the page.
café.tsx, a b.tsx Never matched during client navigation: the client compared the encoded /caf%C3%A9 with the raw pattern.
A clashing page added while void dev runs The dev server process exited.

What changes

scanPages
  same route / same page id   → one error, every clash listed, sorted
  [id] + [slug] (same URLs)   → one warning; the first page always wins today
  framework app (Nuxt …)      → no check: users.vue + users/index.vue is Nuxt nesting

manifest
  layout id == page id        → that layout is keyed "_layout" (only then)

client navigation
  URL → pathname → decodePagePath() → matchRoute(path)
  decodePagePath copies Hono's getPath decoding (no regex)

void dev
  scan error → logged once, last good route table kept

Example error:

pages: Multiple page files resolve to the same route:
  "/about": "about.tsx", "about/index.tsx"
  "/team": "(a)/team.tsx", "(b)/team.tsx"
Keep one page file per route and rename or remove the others.

Notes:

  • Apps without a clash get the same ids and manifest rows. The generated matchRoute now takes a decoded pathname. The engine and matchAdminRoute follow that.
  • The dev guard keeps the last good route table for any scan error, not only page clashes.
  • No repo playground has a clash: all 17 pages dirs were scanned.

Tests

  • pages-scan.test.ts: the route clash error (exact text), the page id clash, the warning printed once across rescans, and no false positive for /users/[id]/edit next to /users/[userId]/posts or for layout.tsx + layout/index.tsx.
  • scan.test.ts: a Void app with users.vue + users/index.vue throws, and a Nuxt app does not.
  • pages-manifest.test.ts: the layout folder case. Encoded paths are checked against a real Hono app for 19 URLs (%C3%A9, %c3%a9, %20, %25, %2525, %2F, %zz, over-encoded ASCII, query/hash), with and without a root [slug].
  • plugin-routing.test.ts: moving about.tsx to about/index.tsx in dev logs once, keeps the old table, and recovers on unlink. Nothing is logged when an adapter pages plugin is present.
  • Each new test fails on main. Unit suites (7,564), the browser tests for all four adapters and the smart runner's affected integration files pass locally.

Not in this PR (already on main)

  • routes/ has the same silent first-file-wins clash. .dev/.prod pairs are intended there, so it needs its own rule.
  • a-b.tsx and a_b.tsx, each with a .server.ts, both become _pload_a_b (dev SyntaxError).
  • ISR page TTLs compare the encoded pathname with the raw pattern (@void/edge matchSegmentPattern).
  • An unnamed catch-all [...].tsx matches the bare /files on the server but not on the client.

🤖 Generated with Claude Code

- scanPages throws one error listing every route or page id that more
  than one page file resolves to (about.tsx + about/index.tsx, .tsx/.vue
  twins, the same page in two route groups, index.tsx + index/index.tsx).
  These had no stable winner: the server and the client could each pick
  a different file. Dynamic pages that match the same URLs ([id] and
  [slug]) only warn, once, since the first one always wins today.
- The checks are skipped when another framework owns pages/ (Nuxt nests
  users.vue and users/index.vue).
- A page in a folder named layout (layout/index.tsx) no longer loads the
  layout beside it: only then is that layout keyed "_layout" in the
  manifest. Nothing changes for apps without the clash.
- Client navigation decodes the path the way the server (Hono) does
  before matching, so pages such as café.tsx or "a b.tsx" match on the
  client. The generated matchRoute and matchAdminRoute take that path.
- void dev logs a page scan error and keeps the last good route table
  instead of exiting, and leaves the log to the adapter pages plugin
  when it reports the same error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T04:48:24.424063Z bad5e80 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-pages-route-clashes-void-cloud.voidzero-docs.workers.dev (commit bad5e80)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://ad1f6f6f-void-cloud.voidzero-docs.workers.dev bad5e80 2026-10-22T04:45:53.492Z Visit the dashboard ↗

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: bad5e80b69

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit cedcd98 into main Oct 22, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/pages-route-clashes branch October 22, 2026 05: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