Type something to search...
How to set up redirects in Astro?

How to set up redirects in Astro?

To set up redirects in Astro, add a redirects object to astro.config.mjs that maps old paths to new ones, such as "/old-about": "/about". That covers permanent moves, dynamic patterns like "/blog/[...slug]": "/articles/[...slug]" and external URLs. For redirects that depend on the request, such as sending logged-out visitors to a login page, return Astro.redirect("/login") from an on-demand page, or call redirect() from an endpoint or middleware. How a configured redirect is delivered depends on your output: a static site without an adapter gets small HTML pages with a meta refresh, while a server or supported adapter sends real HTTP redirects with status codes.

Redirects matter whenever URLs change: a site migration, a renamed section, a short link. Get them wrong and you lose search rankings and frustrate visitors with 404s. In this article you'll learn how to configure static, dynamic and external redirects, how to choose status codes, what Astro actually outputs in static and server builds, how to redirect dynamically from pages, endpoints and middleware, and a priority behaviour in Astro 7.4.2 that differs from what the docs describe.

Configured Redirects

The redirects option is a map from the old path to the new destination:

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  redirects: {
    "/old-about": "/about",
    "/blog/[...slug]": "/articles/[...slug]",
    "/twitter": "https://example.com/@me",
    "/sale": { status: 302, destination: "/offers" },
  },
});

Each entry is one of three kinds:

  • Internal: "/old-about": "/about" points one path at another page on your site.
  • Dynamic: "/blog/[...slug]": "/articles/[...slug]" uses route parameters, so every URL under /blog/ maps to the matching URL under /articles/.
  • External: any destination starting with http or https that can be parsed as a URL sends visitors to another site.

The value can be a string, which uses the default status, or an object with status and destination when you want a specific code.

Rules for Dynamic Redirects

Dynamic redirects follow the same bracket syntax as file-based routing. Both sides must use the same parameters:

// allowed: same parameter on both sides
"/blog/[...slug]": "/articles/[...slug]",
"/docs/[page]": "/guides/[page]",

// not allowed: static source, dynamic destination
// "/article": "/blog/[...slug]",

The config reference also notes that trailing slash variants such as mapping /product1/ to /product1 aren't supported. Trailing slashes are handled by the trailingSlash option instead.

In static mode, a dynamic redirect only produces files for paths the destination route actually builds. My /articles/[...slug].astro page returned two paths from getStaticPaths(), hello and 2026/news, and the build created matching redirect pages at /blog/hello/ and /blog/2026/news/. If you're new to that pattern, see rest parameters for catch-all routes.

What Astro Outputs in a Static Build

Without an adapter, there's no server to send a redirect status, so Astro writes an HTML file for each redirect. I built the config above and the redirect for /old-about became dist/old-about/index.html:

<!doctype html><title>Redirecting to: /about</title><meta http-equiv="refresh" content="0;url=/about"><meta name="robots" content="noindex"><link rel="canonical" href="/about"><body><a href="/about">Redirecting from <code>/old-about/</code> to <code>/about</code></a></body>

The page does four things:

  1. Meta refresh: content="0;url=/about" sends the browser on immediately.
  2. noindex: asks search engines not to index the redirect page itself.
  3. Canonical link: points search engines at the destination.
  4. A fallback link: for the rare client that ignores meta refresh.

The browser receives a 200 response followed by a client-side redirect, not a 301. The status code you set in the config isn't used. Search engines generally follow meta refresh redirects, but a real HTTP redirect is the stronger signal, which is why server-side redirects are worth it for important moves.

Real Redirects on a Static Site

You have two ways to get proper status codes while keeping a static site:

  • Use an adapter: the docs note that supported adapters write the host's own redirect configuration instead of HTML pages. The configured status is then respected.
  • Use your host's redirect file: hosts such as Netlify and Cloudflare Pages read a _redirects file from the output folder. Put it in public/ and Astro copies it into dist/ unchanged.
# public/_redirects
/old-about  /about  301
/sale       /offers 302

Check your host's documentation for the exact syntax it supports.

Redirects with an Adapter

With an adapter, the server handles configured redirects at request time. I added the Node adapter to the same config:

// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";

export default defineConfig({
  adapter: node({ mode: "standalone" }),
  redirects: {
    "/old-about": "/about",
    "/blog/[...slug]": "/articles/[...slug]",
    "/twitter": "https://example.com/@me",
    "/sale": { status: 302, destination: "/offers" },
  },
});

The build no longer wrote redirect HTML files. Testing the running server with curl -s -o /dev/null -w '%{http_code} %{redirect_url}':

/old-about       301 http://localhost:4331/about
/sale            302 http://localhost:4331/offers
/blog/2026/news  301 http://localhost:4331/articles/2026/news
/twitter         301 https://example.com/@me

And for a POST request to the same redirect:

curl -s -X POST -o /dev/null -w '%{http_code}\n' http://localhost:4331/old-about
308

That's the documented behaviour: GET requests get 301 by default, and other methods get 308, which tells the client to repeat the request with the same method and body.

Choosing a Status Code

StatusMeaningUse it for
301Moved permanentlyRenamed pages, site migrations (default for GET)
308Permanent, keep the methodPermanent moves of form or API URLs (default for other methods)
302Found (temporary)Short-term campaigns, A/B tests
307Temporary, keep the methodTemporary moves where the method matters

Use permanent codes when the old URL is gone for good, so search engines move their ranking signals to the new URL. Use temporary codes when you plan to switch back.

Dynamic Redirects with Astro.redirect()

Configured redirects are fixed. When the decision depends on the request, such as a cookie, a header or a database lookup, redirect from the page itself. This needs on-demand rendering:

---
// src/pages/account.astro
export const prerender = false;

const session = Astro.cookies.get("session")?.value;

if (!session) {
  return Astro.redirect("/login");
}
---
<h1>Your account</h1>

Against the built server:

/account (no cookie)      302 http://localhost:4331/login
/account (session=abc)    200 <h1>Your account</h1>

Astro.redirect() defaults to 302 for on-demand routes, and you can pass a second argument to change it, for example Astro.redirect("/new", 301). Two rules apply:

  • Return it: you must return the result. Calling it without returning does nothing.
  • Pages only, not components: because Astro streams HTML on demand, the redirect must happen in the page's frontmatter, not inside a child component, where the response may already have started.

In a prerendered page, Astro.redirect() produces the same meta refresh HTML as a configured redirect and ignores the status code. For real login flows, see how to protect routes in Astro.

Redirecting from an Endpoint

Endpoints receive a redirect() helper on their context. That makes them a neat way to build short links:

// src/pages/go/[id].ts
import type { APIRoute } from "astro";

export const prerender = false;

const links: Record<string, string> = {
  docs: "https://docs.astro.build/",
  repo: "https://github.com/withastro/astro",
};

export const GET: APIRoute = ({ params, redirect }) => {
  const target = links[params.id ?? ""];
  if (!target) {
    return new Response("Unknown link", { status: 404 });
  }
  return redirect(target, 307);
};
/go/docs  307 https://docs.astro.build/
/go/zzz   404

Endpoints are covered in full in how to build API endpoints in Astro.

Redirecting from Middleware

To redirect a whole group of URLs with your own logic, use middleware. The context object has the same redirect() helper:

// src/middleware.ts
import { defineMiddleware } from "astro:middleware";

export const onRequest = defineMiddleware((context, next) => {
  if (context.url.pathname.startsWith("/legacy-shop")) {
    return context.redirect("/offers", 301);
  }
  return next();
});
/legacy-shop/item  301 http://localhost:4331/offers

There's no legacy-shop page at all; the middleware answers before routing reaches a page. Middleware runs for on-demand requests in production, and for prerendered pages only at build time, so use it for redirects on server-rendered sites. Astro middleware explains how it fits into the request pipeline.

In Astro 7, redirects are also a stage in the default request pipeline, which you can reorder with a custom src/fetch.ts file using the redirects() handler from astro/fetch. Most sites don't need that; the config and middleware cover nearly every case.

Redirects and Existing Pages

The Astro routing docs state that configured redirects follow the same priority rules as file-based routes and that file-based routes take precedence over redirects, so /old-page shouldn't redirect if src/pages/old-page.astro exists.

My test on Astro 7.4.2 didn't match that. I added "/clash": "/about" alongside a real src/pages/clash.astro page:

  • Static build: dist/clash/index.html contained the redirect HTML, not the page.
  • Dev server: /clash returned 301 to /about.
  • Node adapter: /clash returned 301 to /about.

In every case, the redirect won. Whichever behaviour is intended, the safe approach is simple: don't define a redirect for a path that still has a page. Delete or rename the old page when you add its redirect. The general priority rules are covered in route priority in Astro.

Migration Checklist

When you move content, work through these steps:

  1. List every old URL: export them from your analytics, sitemap or old CMS.
  2. Map patterns, not just pages: one dynamic rule like /blog/[...slug] can replace hundreds of entries.
  3. Pick the right delivery: use an adapter or your host's redirect file if you want real 301 responses.
  4. Remove the old pages: avoid a page and a redirect on the same path.
  5. Update internal links: link straight to the new URLs instead of relying on redirects.
  6. Test the production build: check status codes with curl -I against astro preview or your deployed site.

FAQ: Redirects in Astro

In the redirects option of astro.config.mjs, as a map from the old path to the new path or URL. For request-dependent redirects, use Astro.redirect() in an on-demand page, or redirect() in an endpoint or middleware.

With an adapter, yes: GET requests get 301 by default and other methods get 308. A static build without an adapter writes HTML pages with a meta refresh instead, so no status code is sent.

Yes. Any destination that starts with http or https and is a valid URL works, for example "/twitter": "https://example.com/@me".

Use a dynamic redirect with a rest parameter on both sides, such as "/blog/[...slug]": "/articles/[...slug]". The parameter names must match on both sides.

It uses 302 by default for on-demand routes. Pass a second argument, such as Astro.redirect("/new", 301), to change it. Remember to return the result from the page frontmatter.

Avoid it. The docs say the page should win, but in my Astro 7.4.2 tests the redirect took over in the static build, the dev server and the Node adapter. Remove the old page when you add the redirect.


Conclusion

Astro gives you redirects at three levels. The redirects config handles permanent moves, folder-wide patterns and external links in one place. Astro.redirect() and the redirect() helper handle decisions that depend on the request, in pages, endpoints and middleware. Together they cover everything from a renamed page to a full site migration.

The detail that matters most is delivery. A static build without an adapter writes meta refresh pages and ignores status codes, while an adapter or your host's redirect file sends real 301 and 302 responses. Choose the delivery that fits your host, keep pages and redirects from overlapping, and always test the production build with curl before you ship.

Tags :
Share :

Related Posts

What are Astro Actions and how do they work?

What are Astro Actions and how do they work?

Astro Actions are type-safe backend functions that you define once on the server and then call from the browser as if they were ordinary async fu

Dive Deeper
What are Astro adapters and which one should you choose?

What are Astro adapters and which one should you choose?

An Astro adapter is an integration that builds the server part of your project for a specific runtime, such as Node.js, Vercel Functions, Netlify

Dive Deeper
How to build API endpoints in Astro?

How to build API endpoints in Astro?

To build an API endpoint in Astro, create a .js or .ts file in src/pages/ and export a function named after an HTTP method, such as GET or

Dive Deeper