Repository navigation
feat(routing): add opt-in route-checked URL strings - #662
Conversation
Add `routing.typedRoutes` to `void.config.ts`. When it is on, URL strings in `Link`'s `href`, `router.visit/prefetch/flush` and `c.rewrite()` must match one of the app's routes. Dynamic segments accept any text, and a query string or hash may follow. Links may also point to same-page queries and hashes and to http(s), mailto and tel URLs. With the flag off, the types are unchanged. `void/routes` exports `RouteUrl` (a URL for one of the app's routes, returned by `routeUrl()` and accepted by `c.rewrite()`) and `Href` (anything a link can point to). The generated `.void/routes.d.ts` turns the check on through a `RouteTypesConfig` interface, so it works whether `void/routes` resolves to the generated file or to the package. `RewriteDestination` stays unchanged because `routing.rewrites` and `routing.fallbacks` destinations can contain placeholders and static files. The Vue `Link` now exposes typed props instead of `Component`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Awesome. imho this is clearly a better default. I don't really see any downsides to making this the default besides that it's a breaking change. What do you think about turning this on for all new projects created via One issue: Vue Link now rejects existing native attributes and events because the new props type for Vue omits them and it causes some issues with |
|
Ya I'm still reviewing, not ready for review yet oops.
Yes we can turn it on. Do it while it's still early.
…On Mon, 2 Nov 2026, 3:23 pm Christoph Nakazawa, ***@***.***> wrote:
*cpojer* left a comment (voidzero-dev/void#662)
<#662 (comment)>
Awesome. imho this is clearly a better default. I don't really see any
downsides to making this the default besides that it's a breaking change.
What do you think about turning this on for all new projects created via void
init?
One issue: Vue Link now rejects existing native attributes and events
because the new props type for Vue omits them and it causes some issues
with strictTemplates enabled in Vue (This usage now fails: <Link href="/"
target="_blank" @click="handler">Home</Link>). Can you fix that?
—
Reply to this email directly, view it on GitHub
<#662?email_source=notifications&email_token=AAKBCHKFZXPCCVGJNRV7BDL5XAZAXA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTMNJXGE2DSNZQGM22M4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-6571497035>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKBCHP2C6ZHDLOJIY3JGO35XAZAXAVCNFSNUABGKJSXA33TNF2G64TZHMYTCNRUGQZTOMBYGA5US43TOVSTWNRRGU3TSNRWGA3TNILWAI>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
- Resolve relative router and Link URLs such as `?page=2` against the current page instead of the origin, in the shared router facade, the React client and all four Link click handlers. - Include default-export route files in RoutePathMap. They are served through `app.all()`, so links and rewrites to them are valid. - Let a trailing `/*` pattern also accept its bare prefix, as Hono does. - Document that dynamic segments can match empty or slash-leading text: template literal types cannot exclude it without per-character unions. - Type the Vue Link's pass-through anchor/button attributes and listeners, and check its written-out props against the runtime prop options. - Mention the `as RouteUrl` cast for runtime-built rewrite paths, and reuse the exported TypedRoutes helper in handler.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`void init` (and `create-void`, which runs it) now writes
`routing: { typedRoutes: true }` when it scaffolds a new Pages app, so
Link hrefs and router URLs are checked against the app's routes from the
start. Running `void init` in an existing project leaves the setting
unchanged.
The packed-consumer check asserts the scaffolded config turns it on, so its
build and typecheck run with checked routes. Docs and the app skill say new
apps have it on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`routeUrl('/café menu')` returns `/caf%C3%A9%20menu`, but typed routes only
accepted the raw spelling, so a written or copied encoded path failed to
typecheck. Codegen now emits an `EncodedRoutePathMap` with the encoded
spelling of each pattern whose fixed segments need encoding, serialized the
same way as `routeUrl()`, and `RouteUrl` accepts both spellings. Dynamic
segments are unchanged. Only the uppercase hex the URL serializer writes is
accepted.
A new fixture checks the package's own route URL types without a generated
declaration, since the generated copy otherwise takes precedence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill already routes Pages work to the type-safety guide, which covers checked URL strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The starter template now includes a generated void.config.ts with routing.typedRoutes turned on, instead of a separate init step that read and rewrote the config. Later init steps still merge database and worker settings into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The React, Vue, Svelte and Solid Pages playgrounds now check Link and router URL strings against their routes. Also drop the packed-consumer assertion on the scaffolded config; the init tests already cover it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yangshun
left a comment
There was a problem hiding this comment.
Feel free to make any docs changes
|
Dude you are killing me requesting a review and then moving it back to draft :P |
Yes I'm sorry for that, spotted some other things that I wanted changed. Take your time, don't need to review so fast haha. |
|
We move fast here! This is awesome, thanks for making this change. We should maybe consider adding a feature to |
|
Ya. I actually think it's ok to make it the default. It's a breaking change, but it flags a real error and people should fix it. WDYT? |
|
We are trying to be careful about breaking changes as we want to provide a stable platform that people can depend on. In a way what we just did is similar to Cloudflare's compatibility date - new projects get the new default automatically. I think that's fine for now. |
Follow-up to #646: add route checking for plain URL strings, alongside untyped strings and destination objects. It's opt-in for existing apps and on by default for new apps.
With
routing.typedRouteson, URL strings inLink'shref,router.visit(),router.prefetch(),router.flush()andc.rewrite()must match one of the app's routes. Each dynamic segment accepts any text, and a query string or hash may follow. Fixed segments can be written as is (/café menu) or percent-encoded, asrouteUrl()returns them (/caf%C3%A9%20menu). Links may also point to same-page queries and hashes and tohttp://,https://,mailto:andtel:URLs. Relative paths are rejected. With the flag off, the types are unchanged.void/routesexports two new types. Both arestringwhen the flag is off:RouteUrl: the URL of one of the app's routes.routeUrl()returns it, andc.rewrite()accepts it.Href: anything a link can point to. It'sRouteUrl, a same-page query or hash, or an external URL.Link'shrefandRouteUrlInputuse it.New apps.
void init(andcreate-void, which runs it) writesrouting: { typedRoutes: true }when it scaffolds a new Pages app. Runningvoid initin an existing project leaves the setting unchanged.Also in this PR:
?page=2andLinkdatamerged into a#hashhref now stay on the current page. Before, they went to/?page=2. Relative URLs now resolve against the current page instead of the origin, in the shared router, the React client and all fourLinkclick handlers./*also accepts its bare prefix (/docs/*accepts/docs), as Hono does.Linktypes: it now declares the anchor and button attributes and listeners it passes through (target,download,@click, …). They were rejected under strict template checking even with the flag off.Notes for review:
.void/routes.d.tsturns the check on through aRouteTypesConfiginterface, the same augmentation pattern asRoutePathMap. Encoded spellings come from a generatedEncodedRoutePathMap. Both work whethervoid/routesresolves to the generated file or to the package.RewriteDestinationis unchanged.routing.rewritesandrouting.fallbacksdestinations can contain:splatplaceholders and static files. Onlyc.rewrite()'s parameter narrows.Linkcast: the exported type isDefineSetupFnComponent<LinkProps>. The options component isn't assignable to it, so it keeps a cast. A compile-time check next to the props fails if the written-out props drift from the runtime ones. It can't live in a test, because isolatedDeclarations prevents exporting the runtime props./-leading text, so with a root dynamic page//example.compasses. Excluding that would need per-character unions. It's documented and pinned by a fixture.routeUrl()writes it, is accepted.void.config.tsisn't watched in dev, so changing the flag needs a dev restart orvoid prepare. The docs say so.Link.svelteisn't template-checked, since the repo has nosvelte-check. Its props type lives inlink-props.ts, which the fixtures check.Validation:
vp run check: 33/33 tasks passed.Linkhrefin all four adapters. Every new case was confirmed to fail against the previous behaviour.Linknavigation.void initturns on the flag. Its build and typecheck then run with checked routes.vp run test --base origin/main(before the last two commits): 15,501 passed. The 1 failure was a 5s timeout inplugin-cloudflare-v2.test.tsunder load, and it passes on its own. The later commits were verified with their focused suites,vp run checkand the packed checks.