Skip to content

feat(routes): support bracket patterns in typed routing APIs - #747

Open
yangshun wants to merge 1 commit into
voidzero-dev:mainfrom
yangshun:codex/bracket-route-patterns
Open

yangshun wants to merge 1 commit into
voidzero-dev:mainfrom
yangshun:codex/bracket-route-patterns

Conversation

@yangshun

@yangshun yangshun commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

Background

I messed up when I first added routeUrl in #640, the internal route type follows Hono's syntax, but that's not what users see when they create file-based routes.

Developers naturally expect to reuse the parameter patterns they see in filenames:

File Expected API usage
routes/api/users/[id].ts fetch('/api/users/[id]', …)
routes/files/[...path].ts routeUrl('/files/[...path]', …)
routes/chat/[room].ws.ts connect('/chat/[room]', …)
Page companion users/[id].server.ts action('/users/[id]?update', …)

The named catch-all was especially awkward:

// File: routes/files/[...path].ts
routeUrl('/files/:path{.+}', {
  params: { path: 'docs/getting-started' },
});

The inconsistency also affected types: a page named users/[id] couldn’t use that same pattern directly with useParams<'/users/[id]'>() or PageParams<'/users/[id]'>.

To stop this problem before it becomes too widespread, we should add support for file-based route patterns while still allowing Hono syntax for now, then deprecate the Hono syntax in a v2 release or something.

Summary

Support bracket route patterns such as /users/[id] and /docs/[...slug] across generated route types, URL construction, typed HTTP and streaming clients, WebSockets, and page actions. Keep existing generated keys available for compatibility and preserve their inferred contracts.

Update public examples and agent guidance to use bracket patterns. Add regression coverage for parameter encoding, catch-alls, named actions, generated/package declarations, all four adapters, browsers, and Workers.

Validation:

  • vp run check
  • vp run build
  • vp test run — 16,185 passed, 32 skipped (781 files passed, one skipped)
  • vp run --filter ./docs build
  • Focused lint and formatting checks

@cpojer

cpojer commented Nov 8, 2026

Copy link
Copy Markdown
Member

Why are we doing this?

@yangshun

yangshun commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Why are we doing this?

Because now the route annotations are very different from the file paths to the routes.

Currently given such a route: routes/api/users/[id].ts, if you want to use it with routeUrl, it becomes fetch(routeUrl('/api/users/:id', { params: { id: 2 }}), where the latter is Hono syntax. Note the difference here, the former uses bracket syntax but latter uses a colon. This is still not too bad, as it's still quite understandable.

However, for the catch-all URLs, the syntax is kinda obscure — the generated type for routes/files/[...path].ts becomes '/files/:path{.+}.

The goal here is to match what users see in the file directory for better readabilty.

This branch has not been deployed

No deployments
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