Rewrites
URL rewrites re-dispatch a request at a different internal path without changing the browser's URL. c.rewrite(), c.originalUrl(), and c.isRewritten() are available on every Hono Context the Void runtime hands you. See the Rewrites guide for the full overview, including static routing.rewrites / routing.fallbacks in void.config.ts.
c.rewrite(destination)
Re-dispatches the current request at destination and returns the resulting Response. The browser URL does not change — this is a server-side hop. If destination has no query string, the original request query is preserved. If destination includes a query string, it replaces the original query. destination must start with /; known generated routes autocomplete via RewriteDestination, while arbitrary strings remain accepted for dynamic paths.
Pass a RouteDestination object to check the route pattern and its parameters:
return c.rewrite({
pathname: '/users/:id',
params: { id: 42 },
query: { tab: 'activity' },
});With routing.typedRoutes, a string destination must be a RouteUrl: a URL that matches one of your routes. Cast a path built at runtime, such as c.rewrite(`/${locale}${path}` as RouteUrl).
For a typed redirect, build the URL with routeUrl():
import { routeUrl } from 'void/routes';
return c.redirect(routeUrl('/users/:id', { params: { id: 42 } }));c.redirect() sends the location it is given. The request query is not added to an internal path, a protocol-relative URL, or an absolute URL, so c.redirect('/notes') from /sign-in?redirectTo=/notes goes to /notes. routing.redirects still merge the request query into the destination.
A named action is the exception. Redirecting it to a GET page drops the action key and keeps the page's other query parameters: POST /users/42?delete&tab=profile with c.redirect('/users/42') goes to /users/42?tab=profile. Redirects that preserve the request method keep the action key: 307 and 308, or 301 and 302 after a PUT, PATCH, or DELETE.
import { defineHandler } from 'void';
export const GET = defineHandler((c) => {
const locale = c.req.header('accept-language')?.startsWith('de') ? 'de' : 'en';
return c.rewrite(`/${locale}${new URL(c.req.url).pathname}`);
});Signature:
interface CloudContext {
// RouteUrl instead of RewriteDestination with routing.typedRoutes
rewrite(destination: RewriteDestination | RouteDestination): Promise<Response>;
}Caveats:
destinationmust start with a single/and point at an internal path. External URLs, protocol-relative destinations (//host/...), and anything non-path-absolute throw immediately — usefetch()for cross-origin calls.- Destinations whose final path segment ends in a known static-asset extension (e.g.
/hero.png,/app.css,/data.json) throwVoidAssetRewriteError(exported from"void") before the re-dispatch. Runtime rewrites cannot reach assets — use a staticrouting.rewritesrule or a_redirects200!entry..htmlis deliberately excluded from this guard because.htmlpaths may be legitimate route handlers. See Rewrites. - Middleware re-runs on every rewrite hop. Guard side-effects with
c.isRewritten(). - Client-side SPA navigation via
Linkdoes not trigger server rewrites; only full HTTP requests do. - Purge the destination path to clear rewritten ISR entries.
c.originalUrl() / c.isRewritten()
Context methods for inspecting rewrite state. c.originalUrl() returns the pre-rewrite URL as a URL object, or null on the first, non-rewritten hop. c.isRewritten() returns true on any re-dispatched context and false on the original. Use them for both static and middleware rewrites.
import { defineMiddleware } from 'void';
export default defineMiddleware(async (c, next) => {
if (!c.isRewritten()) {
// Only log the user-visible request, not internal rewrite hops.
console.log(`${c.req.method} ${c.req.path}`);
}
await next();
});Signature:
interface CloudContext {
originalUrl(): URL | null;
isRewritten(): boolean;
}RewriteDestination
import type { RewriteDestination } from 'void/routes';Suggests known route patterns in your editor while also accepting dynamic strings. Used as:
- The parameter type of
c.rewrite(), unlessrouting.typedRoutesis on. - The type of
destinationentries inrouting.rewritesandrouting.fallbacksinvoid.config.ts.
Like RouteMap, RewriteDestination lives in the virtual void/routes module and is refreshed whenever routes change.