--- slug: redirect-maps title: Redirect Maps description: How to configure redirect maps in front of a Waku server. category: Routing and Navigation order: 20 tags: [Experimental] --- ## External Redirect Maps This guide is for redirect maps that sit in front of the Waku server, such as middleware, reverse proxies, CDNs, or hosting-platform redirect rules. When a Waku app uses the client router, a navigation usually fetches both: - the browser route, such as `/old` - the RSC payload for the same route, at a separate url Both have to be redirected. If you redirect only `/old`, a direct visit works while client-side navigation still lands on the old route. Inside the app, `unstable_parseRouterRequest` reads either url as the route it addresses, and `unstable_formatRouterRequest` builds the matching url for the destination, so middleware never has to spell the RSC url out. Outside the app — a CDN or a hosting platform's redirect rules — there is no way to call into Waku, so those rules do have to name the url. With Waku's default config: - `basePath` is `/` - `rscBase` is `RSC` - the route `/old` maps to the RSC path `/RSC/R/old.txt` > The `/RSC/` file naming convention is [subject to change](https://github.com/wakujs/waku/discussions/929#discussioncomment-10825975) in future versions of Waku. Prefer the middleware approach below, which does not depend on it. If you customize `basePath` or `rscBase`, adjust any hand-written rules accordingly. For application-level redirects implemented inside Waku routes or server actions, see [Redirects and Not Found](/guides/redirects-and-not-found) instead. That is a separate use case from the redirect-map approach described here. ### Redirect via middleware Create a new middleware file in `src/middleware/`. Waku automatically discovers and loads middleware from this directory. ```typescript // ./src/middleware/redirects.ts import type { MiddlewareHandler } from 'hono'; import { unstable_formatRouterRequest as formatRouterRequest, unstable_parseRouterRequest as parseRouterRequest, } from 'waku/router/server'; const redirects: Record = { '/old': '/new', // ... add more redirects here }; const redirectsMiddleware = (): MiddlewareHandler => async (c, next) => { // Reads the browser route and the RSC payload request the same way. const parsed = parseRouterRequest(c.req.raw); const destination = parsed?.type === 'route' ? redirects[parsed.path] : undefined; // Rebuilds the same kind of url, so client-side navigation gets a payload // and a direct visit gets the page. `null` means this request cannot be // rewritten without losing state, so leave it alone. const location = destination ? formatRouterRequest(c.req.raw, destination) : null; if (location) { c.res = new Response(null, { status: 302, headers: { location: location.toString() }, }); return; } return await next(); }; export default redirectsMiddleware; ``` Matching on `parsed.path` covers both requests with one entry per route, and keeps the redirect map readable as a list of routes. > In a production build the adapter serves statically generated files before middleware runs, so a middleware redirect does not fire for a route rendered with `render: 'static'`. Redirect those from the hosting environment instead, or make the route dynamic. Note the `type` check: an action dispatched from the client is addressed by function id and carries no route, so a path rule can never match it. A progressively enhanced form submitted without JavaScript is the other way around — it posts to the route's own url, so it parses as `'route'` and a rule for that path does apply to it. Either way, a path rule is a redirect, not an authorization boundary; see [Authentication](/guides/integrations/authentication) for where to put checks that must not be bypassed. ### Redirect via hosting environment A redirect map outside the app cannot call into Waku, so these rules name both urls explicitly and depend on the `/RSC/` convention noted above. This very much depends on the hosting environment you are using. For example, on [Vercel](https://vercel.com/docs/projects/project-configuration#redirects) you can use the `vercel.json` file to define redirects. ```json { "redirects": [ { "source": "/old", "destination": "/new", "permanent": true }, { "source": "/RSC/R/old.txt", "destination": "/RSC/R/new.txt", "permanent": true } ] } ``` [Netlify](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file) and [Cloudflare Pages](https://developers.cloudflare.com/pages/configuration/redirects/) will respect a `_redirects` file that you can place in the `public` folder: ``` /old /new 301 /RSC/R/old.txt /RSC/R/new.txt 301 ``` For app-level redirects inside page code or server actions, use [Redirects and Not Found](/guides/redirects-and-not-found) instead of external redirect maps.