Rewrites
A rewrite serves a route at a different URL without changing the address bar. For example, /docs can serve /en/docs. Use a redirect when the browser should move to the new URL instead.
On this pageAdd rewrite rules in config · Rewrite from a handler · Avoid surprises with client navigation
This step defines a rewrite 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.
Matching 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-alls.
- Rewrites run before static assets and application routes.
Programmatic rewrites in middleware
This step rewrites from middleware with c.rewrite(). Use it when the destination depends on the request, such as a locale 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. Skip rewritten paths to avoid a loop. This step uses c.isRewritten() to run work only once.
if (c.isRewritten()) return next();Keep access checks on any destination that needs them.
A destination without ? keeps the incoming query string. c.rewrite('/search') keeps ?q=.... 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 only. To serve an asset such as /hero.png at another URL, use routing.rewrites or a _redirects rule. A middleware rewrite to a recognized asset extension throws VoidAssetRewriteError.
Original URL access
c.originalUrl() returns the URL before any rewrite, or null if none happened. This step uses it for a canonical link.
import { defineHandler } from 'void';
export default defineHandler((c) => {
const original = c.originalUrl();
return c.json({ pathname: original?.pathname ?? c.req.path });
});Fallbacks
This step adds a fallback. routing.fallbacks uses rewrite patterns, 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 an SDK route stays a 404. With third-party frameworks, a fallback can replace the framework's 404.
SPA apps already fall back to /index.html. Custom fallbacks run first. Other paths still get the SPA shell. You do not need to add '/*': '/index.html'.
_redirects file
This step puts rules in a file in Vite's publicDir. It 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 win 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"> can fetch loader data for /docs, not for the rewritten /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
This step helps you debug rules in development. Check the X-Void-Routing header in the browser's Network tab. It shows the matching rule and where it is 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