Redirects
Redirects let you send visitors from one URL to another, whether because content moved, a route was renamed, or a request needs to be steered based on logic that only the server knows. Astro supports two complementary approaches: declarative redirects defined in astro.config.mjs that are baked in at build time, and imperative redirects issued at runtime with Astro.redirect() in server-rendered pages. Getting redirects right preserves SEO ranking, keeps bookmarks working, and avoids broken links after a refactor.
Configured redirects
The simplest way to declare a redirect is the top-level redirects option in your config. Each key is the source path and each value is the destination. Astro resolves these at build time and, depending on your output mode, emits either static HTML meta-refresh pages or proper server-side 301 responses.
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
redirects: {
"/old-blog": "/blog",
"/team": "/about/team",
"/docs/v1": "/docs/latest",
},
});
In a fully static build (output: 'static'), each redirect becomes a tiny HTML page containing a <meta http-equiv="refresh"> tag plus a canonical link. When an SSR adapter is present, Astro instead generates real HTTP redirects with a 301 Moved Permanently status, which is what search engines and browsers prefer.
Many hosts (Netlify, Vercel, Cloudflare) translate the config
redirectsmap into native platform redirect rules during the build, so they run at the edge with zero rendering cost. Check your adapter docs to confirm.
Dynamic segments in configured redirects
Redirect sources and destinations can contain dynamic parameters. A [param] segment captured in the source can be re-used in the destination, which is ideal for migrating a whole URL scheme at once.
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
redirects: {
"/articles/[id]": "/blog/[id]",
"/u/[username]": "/profile/[username]",
// rest parameters work too
"/legacy/[...path]": "/new/[...path]",
},
});
Choosing the status code
By default configured redirects use 301. To use a different code, expand the value into an object with status and destination.
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
redirects: {
"/promo": { status: 302, destination: "/sale" },
"/checkout": { status: 307, destination: "/cart" },
},
});
| Status | Meaning | Method preserved? | Use when |
|---|---|---|---|
301 | Moved Permanently | No (may switch to GET) | Permanent URL change; SEO-friendly |
302 | Found (temporary) | No | Short-lived redirect (campaign, A/B) |
307 | Temporary Redirect | Yes | Temporary, must keep POST/PUT |
308 | Permanent Redirect | Yes | Permanent, must keep method |
Runtime redirects with Astro.redirect
When the destination depends on data only available at request time, such as authentication state, feature flags, or a database lookup, you need an on-demand rendered page and the Astro.redirect() helper. It returns a Response object that you must return from the component script.
---
// src/pages/dashboard.astro
const session = await getSession(Astro.request);
if (!session) {
return Astro.redirect("/login");
}
// You can also specify a status code
const user = await loadUser(session.userId);
if (!user.onboarded) {
return Astro.redirect("/onboarding", 302);
}
---
<h1>Welcome back, {user.name}</h1>
The page above must be server-rendered. With output: 'static' (the default in Astro 5) you opt a single page into on-demand rendering by exporting prerender = false, or set output: 'server' for an SSR-by-default site.
---
// src/pages/account.astro
export const prerender = false;
const session = await getSession(Astro.request);
if (!session) return Astro.redirect("/login", 307);
---
<h1>Your account</h1>
Redirecting from API endpoints
Endpoints (.ts route files) return Response objects directly, so you can build a redirect with the standard Response.redirect API or by setting a Location header.
// src/pages/go/[slug].ts
import type { APIRoute } from "astro";
const links: Record<string, string> = {
twitter: "https://twitter.com/devcraftly",
github: "https://github.com/devcraftly",
};
export const prerender = false;
export const GET: APIRoute = ({ params, redirect }) => {
const target = links[params.slug ?? ""];
if (!target) {
return new Response("Not found", { status: 404 });
}
return redirect(target, 302);
};
A request to /go/github responds with:
Output:
HTTP/1.1 302 Found
Location: https://github.com/devcraftly
Astro.redirect()and the endpointredirect()helper return aResponse— they do not halt execution by themselves. Alwaysreturnthe value, or code below it will keep running and may throw when it tries to render.
Static vs runtime: which to use
| Aspect | Configured (redirects) | Runtime (Astro.redirect) |
|---|---|---|
| Defined in | astro.config.mjs | Page/endpoint script |
| Decision time | Build time | Per request |
| Needs SSR? | No (static emits meta-refresh) | Yes (prerender = false) |
| Best for | Known, fixed URL moves | Auth, data-driven logic |
| Edge support | Often native via adapter | Runs in the server function |
Best Practices
- Prefer configured
redirectsfor known, permanent URL changes — they are simpler, cacheable, and frequently run natively at the edge. - Use
301/308for permanent moves so search engines transfer ranking; reserve302/307for genuinely temporary redirects. - Choose
307/308whenever the original HTTP method (POST, PUT) must be preserved across the redirect. - Always
returntheResponsefromAstro.redirect()so the rest of the script does not execute. - Set
export const prerender = false(oroutput: 'server') on any page that issues runtime redirects, since static pages cannot make per-request decisions. - Avoid redirect chains and loops; redirect straight to the final destination to keep requests fast and crawlable.
- Keep redirect maps under version control and audit them after each refactor to prune stale entries.