Rewrites
A rewrite serves a route at a different URL without changing the browser's address. For example, /docs can serve /en/docs. Use a redirect when the browser should move to the destination instead.
Define rewrites in void.config.ts:
import { defineConfig } from 'void/config';
export default defineConfig({
routing: {
rewrites: {
'/': '/en',
'/docs': '/en/docs',
'/docs/*': '/en/docs/:splat',
},
},
});A request to /docs/getting-started serves /en/docs/getting-started. Other locales, such as /ja/docs/getting-started, keep their own routes.
Rules
- Sources and destinations start with
/. Destinations must be paths within the app. *matches any characters, including/. Use:splatin the destination for the matched portion.- The first matching rule wins. Put specific patterns before catch-all patterns.
- Rewrites run before static assets and application routes.
Programmatic rewrites in middleware
Use c.rewrite() when the destination depends on the request, such as a locale chosen from a cookie:
import { defineMiddleware } from 'void';
export default defineMiddleware(async (c, next) => {
if (c.req.path.match(/^\/(en|ja)(\/|$)/)) return next();
const locale = detectLocale(c.req);
return c.rewrite(`/${locale}${c.req.path}`);
});Return the result of c.rewrite(). Middleware runs again at the destination, so skip paths that are already rewritten to avoid a loop. Use c.isRewritten() to skip work that should happen only once:
if (c.isRewritten()) return next();Keep access checks on any destination that needs them.
A destination without ? preserves the incoming query string. c.rewrite('/search') keeps ?q=..., while c.rewrite('/search?q=all') replaces it.
Your editor suggests known routes through the RewriteDestination type. Dynamic strings are also accepted.
Runtime rewrites cannot reach static assets
c.rewrite() targets application routes. To serve an asset such as /hero.png at another URL, use routing.rewrites or a _redirects rule instead. A middleware rewrite to a recognized asset extension throws VoidAssetRewriteError.
Original URL access
c.originalUrl() returns the URL requested before a static or middleware rewrite, or null when no rewrite occurred. Use it for canonical links or locale detection:
import { defineHandler } from 'void';
export default defineHandler((c) => {
const original = c.originalUrl();
return c.json({ pathname: original?.pathname ?? c.req.path });
});Fallbacks
routing.fallbacks uses the same patterns as rewrites, but applies only when no asset or route matches:
import { defineConfig } from 'void/config';
export default defineConfig({
routing: {
fallbacks: { '/*': '/index.html' },
},
});Use a fallback for a client-side router or a default-locale catch-all. An intentional 404 from a Void route remains a 404. For third-party frameworks, fallbacks can also replace the framework's 404 response.
SPA apps already fall back to /index.html. Custom fallback rules run first; unmatched paths still use the SPA shell. You don't need to add '/*': '/index.html' yourself.
_redirects file
You can also place rules in Vite's publicDir, which defaults to public/:
# Fallback: only when no asset or route matches
/* /index.html 200
# Rewrite: before assets and routes
/docs/* /en/docs/:splat 200!The file can include 3xx redirects too. Rules in void.config.ts take precedence over file rules; within each group, the first match wins.
Client navigation
Rewrites run on the server. In Pages mode, a client-side <Link href="/docs"> navigation may fetch loader data for /docs rather than the rewritten route /en/docs.
Use <a href="/docs"> to make a full server request when the destination has a different loader. Use a redirect if the URL should change.
ISR cache keys with rewrites
Purge the destination path, such as /en/docs/foo, to clear rewritten cache entries. See Cache keys and rewrites.
Debugging with X-Void-Routing
During development, inspect the X-Void-Routing response header in your browser's Network tab. It shows the matching rule and where it was declared:
X-Void-Routing: rewrite[/docs/*] -> /en/docs/:splat (void.config.ts#routing.rewrites)
X-Void-Routing: c.rewrite -> /new-path (middleware)
X-Void-Routing: pass-through