You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit cedcd98
Browse filesBrowse the repository at this point in the historyBrowse files
fix(pages): report page route clashes and match encoded page paths (#545)
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](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/guide/pages-routing/overview.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -94,6 +94,8 @@ Pages can be flat files such as `about.vue` or directory-based routes such as `a
94
94
95
95
<PagesFileTree />
96
96
97
+
Keep one file per route. Void reports an error when two page files resolve to the same path, such as `about.vue` and `about/index.vue`, or the same page in two route groups.
98
+
97
99
Each page can have a companion `.server.ts` file that runs exclusively on the server. It can export:
98
100
99
101
- A [**loader**](./loaders), which runs on `GET` and returns the data that becomes the page component's props
`pages: Page '${page.componentPath}' (${page.pattern}) is never used: '${used.componentPath}' (${used.pattern}) matches the same URLs first. Remove one of them.`,
495
+
);
496
+
}
497
+
}
498
+
}
499
+
408
500
// Same leading-script rule as @void/md's extractScript: a markdown page may open
409
501
// with a <script> block, and its frontmatter starts right after that block.
0 commit comments