Skip to content
Astro as routing 4 min read

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 redirects map 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" },
  },
});
StatusMeaningMethod preserved?Use when
301Moved PermanentlyNo (may switch to GET)Permanent URL change; SEO-friendly
302Found (temporary)NoShort-lived redirect (campaign, A/B)
307Temporary RedirectYesTemporary, must keep POST/PUT
308Permanent RedirectYesPermanent, 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 endpoint redirect() helper return a Response — they do not halt execution by themselves. Always return the value, or code below it will keep running and may throw when it tries to render.

Static vs runtime: which to use

AspectConfigured (redirects)Runtime (Astro.redirect)
Defined inastro.config.mjsPage/endpoint script
Decision timeBuild timePer request
Needs SSR?No (static emits meta-refresh)Yes (prerender = false)
Best forKnown, fixed URL movesAuth, data-driven logic
Edge supportOften native via adapterRuns in the server function

Best Practices

  • Prefer configured redirects for known, permanent URL changes — they are simpler, cacheable, and frequently run natively at the edge.
  • Use 301/308 for permanent moves so search engines transfer ranking; reserve 302/307 for genuinely temporary redirects.
  • Choose 307/308 whenever the original HTTP method (POST, PUT) must be preserved across the redirect.
  • Always return the Response from Astro.redirect() so the rest of the script does not execute.
  • Set export const prerender = false (or output: '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.
Last updated June 14, 2026
Was this helpful?