Skip to content

fix(void): register static routes before dynamic siblings - #220

Merged
Brooooooklyn merged 2 commits into
mainfrom
fix/route-static-precedence
Jun 30, 2026
Merged

Brooooooklyn merged 2 commits into
mainfrom
fix/route-static-precedence

Conversation

@hyfdev

@hyfdev hyfdev commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Static routes now reliably win over dynamic siblings: /users/new is served by its own handler instead of falling through to /users/[id].

The bug

The route scanner sorted overlapping routes with pattern.localeCompare. Its comment promised "static routes before dynamic, catch-all last" — but : (0x3A) sorts before most letters, so /users/:id was ordered ahead of /users/new. The generated Hono app registers routes in scan order (compile.ts app.<method>(pattern) loop), and Hono's default router resolves overlapping routes by registration order — so a request to /users/new was wrongly served by the [id] handler (with id = "new").

Expected-correct behavior

A literal segment must beat a dynamic param at the same depth (the standard file-based-routing contract, and what the scanner's own comment already intended). Verified empirically against the bundled Hono (4.12.18, default SmartRouter): registering the static route first resolves /users/new to the correct handler; registering the dynamic one first reproduces the bug.

Minimal repro

Two route files: routes/users/[id].ts and routes/users/new.ts. Before this change the scanner ordered them ['/users/:id', '/users/new'], so GET /users/new hit the [id] handler.

The fix

Replace the localeCompare tiebreaker with a segment-aware specificity comparator (static segment → dynamic :param → catch-all, compared depth-first, then shorter-first), shared by both scan entry points (also removes the previously-duplicated inline sort).

Tests

Regression tests added (packages/void/test/unit/scan.test.ts): a direct static-vs-dynamic case and a nested same-depth case — both fail on origin/main and pass after. Local gate: vp test run packages/void/test/unit/scan.test.ts 18/18; full --project unit zero-collateral vs a clean origin/main baseline (identical pre-existing adapter/init-migration failures, unrelated to routing); packages/void typecheck + lint + fmt clean.

Note: the regression is guarded at the scan-order (unit) layer, which is a faithful proxy for the Hono precedence behavior; there is no playground today with colliding static+dynamic siblings to host an end-to-end dispatch test.

Prepared with AI assistance.

@netlify

netlify Bot commented Jun 18, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for staging-void-cloud failed.

Name Link
🔨 Latest commit f6ca1f6
🔍 Latest deploy log https://app.netlify.com/projects/staging-void-cloud/deploys/6a341803d06aa40008279eb8

@hyfdev
hyfdev marked this pull request as ready for review June 18, 2026 12:10
The route scanner sorted overlapping routes with pattern.localeCompare,
whose comment promised "static routes before dynamic" but did not
deliver: ':' (0x3A) sorts before most letters, so '/users/:id' was
ordered ahead of '/users/new'. The generated Hono app registers routes
in this order, and Hono's default router resolves overlaps by
registration order, so a request to /users/new was served by the
[id] handler instead of the dedicated static route.

Replace the localeCompare tiebreaker with a segment-aware specificity
comparator (static segment before dynamic param before catch-all,
compared depth-first) shared by both scan entry points.
@hyfdev
hyfdev force-pushed the fix/route-static-precedence branch from 8381649 to f6ca1f6 Compare June 18, 2026 16:08
@Brooooooklyn
Brooooooklyn merged commit 8a14c36 into main Jun 30, 2026
6 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/route-static-precedence branch June 30, 2026 03:12
cpojer pushed a commit that referenced this pull request Sep 11, 2026
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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.

2 participants