Skip to content

Redirects and rewrites (_redirects)

A _redirects file in your site tells the network what to do with addresses that have moved, or that don’t match a file. It uses the same format as Netlify and Cloudflare Pages, so a file you already have usually works unchanged.

It applies to Level 0 (public) sites only. See Scope and limits.

Put _redirects in the root of your build output, next to your home page’s index.html. Most generators copy a static folder into the output unchanged, so put the file there:

Generator Put _redirects in
Astro, Vite, Qwik City public/
VitePress docs/public/ (your source folder’s public/)
Hugo, Docusaurus, Gatsby static/
Eleventy Any folder you pass through to the output
Jekyll The site root, with include: [_redirects] in _config.yml

The file travels with your site. It deploys and rolls back with the content it belongs to.

One rule per line: the address to match, where to send it, and an optional status.

# Lines that start with # are comments
/old-page /new-page/ 301
/blog/* /news/:splat 301
/:year/:slug /posts/:slug 301
/shop https://shop.example.com/ 302
  • from is a path on your site, starting with /.
  • to is a path on your site, or a full URL on another site.
  • Status defaults to 301 if you leave it out.
  • * at the end of from matches the rest of the path. Use :splat in to to carry it over.
  • :name matches one path segment. Use the same :name in to.
Status What it does
301 Permanent redirect. The visitor’s address bar changes.
302 Temporary redirect. The visitor’s address bar changes.
200 Rewrite. Serves the target’s content at the address that was asked for. The address bar doesn’t change.
404 Serves the target’s content with a 404 status. Use it for a not-found page for one section. See Custom 404 pages.

307 and 308 aren’t supported.

A rewrite (200) can only serve a file from your own site. It can’t point at another site.

  1. A real file wins over a rule. If your site has a file at the requested path, it’s served, and the rule is skipped. Add ! to the status to force the rule to apply anyway:

    /old /new/ 301!
  2. Exact rules (no * or :name) are checked first.

  3. Pattern rules are checked in the order they appear in the file. The first match wins.

  4. If two rules have the same from, the first one is used.

The first point is what makes a single-page-app fallback work. This rule sends every unknown path to your app, but still serves your real scripts, styles and images:

/* /index.html 200

A path counts as having a real file if the file exists at that path, or if an index.html exists in a folder of that name.

Two optional lines turn on routing behaviour for the whole site. Each is written as a comment, so the file still works on other hosts.

# ee:trailing-slash on

Many generators write each page as a folder with an index.html inside: about/index.html, served at /about/. Without this line, /about (no slash) is a 404. With it, /about redirects to /about/.

It applies to any path whose last part has no dot and no trailing slash. Your own rules are checked first, so it never adds an extra step in front of a redirect you wrote.

With Astro, also set trailingSlash: 'always' and build: { format: 'directory' } in astro.config.mjs, so your site’s own links use the slash.

# ee:clean-urls on

Some generators write flat files (about.html) but link to /about. With this line, /about is served from about.html, and the address bar stays on /about.

Generators that need it: Quartz, Observable Framework, Hono’s SSG helper, vite-ssg, Silex, îles and Emanote by default, and VitePress, Rspress, VuePress and HonKit when their cleanUrls option is on.

  • A real file still wins. /LICENSE serves LICENSE even if LICENSE.html exists.
  • about.html counts as the page at /about. A rule of your own for /about only applies if you force it with !.
  • With both settings on, /about serves about.html when it exists, and redirects to /about/ only when it doesn’t.
  • /about.html still works as well. Your generator’s canonical tag tells search engines which address you prefer.

Move one page:

/pricing-2025 /pricing/ 301

Move a whole section, keeping the rest of each address:

/blog/* /news/:splat 301

Send a section to another site:

/docs/* https://docs.example.com/:splat 301

A single-page app, where every unknown path loads the app:

/* /index.html 200

A not-found page for one section:

/docs/* /docs/404.html 404
  • Level 0 sites only. Encrypted sites (Level 1 and Level 2) don’t read _redirects. See encryption levels.

  • 307 and 308 aren’t supported.

  • Rewrites stay on your site. A 200 rule that points at another site is refused.

  • Conditions aren’t supported. Netlify’s Country=, Language=, Role= and Cookie= conditions, rules that match on query parameters, and rules whose source is a full domain all stop the deploy.

  • Up to 2,000 rules per file.

  • Redirects are cached for 5 minutes by browsers, so a rule you remove stops applying soon after your next deploy.

  • The file isn’t downloadable. A request for /_redirects gets a 404.

  • Some paths belong to the network and can’t be redirected or used for your own files:

    • the files visitors’ browsers use to load your site: /ee.js, /ee-worker.js, /brotli_wasm.wasm;
    • exactly /health and /discover;
    • /_header, /_index, /_blocks, /_torrent, /_digest and anything under /_piece/;
    • anything under /.ee/.

    Paths such as /health/team, /discover/products and /ee/docs/ are yours.

Your file is checked when you deploy, from the portal or the command line. The deploy stops, and names each line, if the file has:

  • a rule with no target, or a target that isn’t a path or a full URL;
  • a status other than 301, 302, 200 or 404;
  • a rule that redirects to itself, or a chain of rules that loops;
  • a rewrite (200) or 404 rule whose target is on another site, or isn’t a file in your site;
  • a condition, a query-parameter match or a domain-level source;
  • a rule for a path that belongs to the network;
  • more than 2,000 rules.

Two rules with the same from only produce a warning, since the first one is used.

The deploy also prints what it found, for example _redirects: 3 rule(s), trailing-slash: on, clean-urls: off. Check that line if a routing setting doesn’t seem to apply: a tool that strips comments can remove the setting without any other sign.

To check a rule after deploying, request the old address and look at the status and Location:

Terminal window
curl -sI https://www.example.com/old-page

Next: Response headers (_headers) >