Netlify redirects are declared either in a plain-text _redirects file in your publish directory or in the configuration file, and the single most important rule is that they are matched top to bottom and the first match wins.
Most redirect problems are not syntax problems. They are ordering problems, or the difference between a redirect and a rewrite, or a force flag that was or was not needed. Getting those three concepts straight solves nearly every case.
Table of contents
- The basic syntax
- Redirect versus rewrite, the distinction that matters
- Ordering and precedence, where the bugs live
- Debugging a rule that will not fire
- What carries to other hosts, and what does not
- How this fits the rest of the stack
- FAQ
The basic syntax
In a _redirects file, one rule per line: the path to match, then the destination, then an optional status code. Lines starting with # are comments, and paths are case-sensitive.
# from to status
/old-page /new-page 301
/blog/* /articles/:splat 301
/docs/:id /documentation/:id 301
/api/* https://api.example.com/:splat 200
Two placeholder types matter. A * is a splat that matches the rest of the path and is available as :splat in the destination. A :name segment matches one path segment and is available under that name.
The status code carries meaning. 301 is a permanent redirect and is what search engines act on. 302 is temporary. 200 is not a redirect at all - it is a rewrite, which is the next section and the source of most confusion.
Redirect versus rewrite, the distinction that matters
A redirect (301 or 302) tells the browser to go somewhere else. The address bar changes, a second request happens, and the user can see the new URL.
A rewrite (status 200) serves different content at the same URL. The address bar does not change, the browser makes one request, and the user has no idea. This is how you proxy an API through your own domain to avoid cross-origin problems, and how single-page application routing works.
The SPA fallback is the most-copied rule on the internet and worth understanding rather than pasting:
/* /index.html 200
It says: for any path that has not already matched a real file, serve the application shell with a 200. The router then reads the URL client-side and renders the right view. If this rule were a 301, every deep link would bounce to the root and lose the path, which is exactly the bug people report when they get the status code wrong.
Crucially, it must be last. It matches everything, and first match wins.
Ordering and precedence, where the bugs live
Rules are evaluated top to bottom and the first match wins. That single sentence explains most redirect bugs, and the corollary is that specific rules go above general ones.
# correct: specific first
/blog/special-post /promo 301
/blog/* /articles/:splat 301
/* /index.html 200
Reverse the first two and the special post never gets its own rule, because the wildcard swallowed it. Move the SPA fallback anywhere but last and nothing below it ever runs.
There is a second precedence layer: when both a _redirects file and a configuration file exist, the file’s rules are processed first, followed by the configuration’s. Splitting rules across both is a reliable way to confuse yourself in six months. Pick one and keep everything in it.
The other trap is the force flag. By default, a rule does not override an actual file that exists at that path - so if /about is a real page, a rule pointing /about elsewhere quietly does nothing. Appending ! to the status forces the rule to win anyway.
/about /company 301!
Debugging a rule that will not fire
- Check what actually happens. Request the URL and read the status and
locationheader directly rather than watching the browser, which will have cached a previous 301 and shown you a stale answer. - Clear the cache, or use a fresh private window. Browsers cache permanent redirects aggressively, which means a wrong 301 you have already visited will keep happening after you fix it. This wastes more time than any other single thing here.
- Look for an earlier match. Read the rules above yours and ask whether any wildcard would have caught this path first.
- Check for a real file. If something exists at that path, the rule needs the force flag.
- Check the trailing slash.
/pathand/path/are different, and the platform may normalise one to the other before your rules see it. - Confirm the file shipped.
_redirectsmust be in the published output directory, not in the project root, unless those happen to be the same place. This catches people constantly.
What carries to other hosts, and what does not
This syntax originated with one platform and has been adopted by several others, which makes basic rules reasonably portable - simple path-to-path redirects with status codes work in more places than you would expect.
What does not carry cleanly: conditional rules based on country, language, or cookie; proxy rewrites to external hosts; role-based access rules; and anything relying on the platform’s specific precedence between configuration sources. These are the rules that break silently on migration, and silently is the operative word - the site works, and one path is wrong.
If portability matters, keep the redirect set small and boring, prefer 301s from old paths to new ones, keep the SPA fallback last, and put genuinely conditional logic in the application rather than in host configuration. A redirect map that is fifteen simple lines moves anywhere; one that encodes routing logic in a proprietary syntax does not.
How this fits the rest of the stack
Redirect rules are one of those things that behave identically right up until you change hosts, which is a good argument for keeping them simple and knowing what they cost you. The RunxBuild hosting calculator prices static hosting alongside any services and databases the site needs, so the full picture is on one page. RunxBuild static sites support custom domains, response headers, redirects, rewrites, and SPA fallback, build from a repository, and include 120GB of bandwidth with $0.10/GB after.
Useful related references:
- Joomla to WordPress Migration: What Transfers, What Does Not, and the Redirects
- Domain Forwarding and SEO: Redirects That Keep Your Rankings
- ERR_TOO_MANY_REDIRECTS: Finding the Loop and Breaking It
- Redirects on RunxBuild
FAQ
Where do I put the _redirects file?
In the publish directory - the folder that gets deployed, not the project root, unless they are the same. This is the single most common reason rules appear to be ignored entirely. Check the build output actually contains the file after a deploy.
What is the difference between a redirect and a rewrite?
A redirect (301 or 302) tells the browser to request a different URL, so the address bar changes. A rewrite (status 200) serves different content at the same URL with no second request and no visible change. Rewrites are how API proxying and SPA routing work.
Why is my redirect rule being ignored?
Usually one of four reasons: an earlier wildcard rule matched first, a real file exists at that path so the rule needs a force flag, the browser cached a previous permanent redirect, or the rules file never made it into the published output directory.
How do I set up SPA fallback routing?
A single rule mapping everything to the app shell with a 200 status, placed last so it only catches paths nothing else matched. Using a 301 instead of a 200 is the usual bug, and it makes every deep link bounce to the root.
Are redirect rules portable between hosting platforms?
Simple path-to-path redirects with status codes usually are, since several hosts adopted the same syntax. Conditional rules by country or cookie, proxy rewrites to external hosts, and role-based access rules generally are not, and they fail silently rather than loudly.