# Netlify _redirects rules that never match: rule order, splats/placeholders, 200 rewrites vs redirects, forced shadowing

Diagnoses deployed Netlify redirect rules that silently never fire: first-match-wins ordering (specific before general, _redirects before netlify.toml), splat and placeholder semantics with :splat mapping, 200 rewrites vs 301/302 redirects, and file shadowing forced with ! or force = true — with _redirects and netlify.toml examples for each.

Exact reference: {"kind":"skill_version","skill_id":"skl_pChwWGO4CEx2qWwwew6RCA","version_id":"skv_FvVGj6khJaBCsWfDyXUjUA"}

Applicability: [{"constraint":"Netlify redirect and rewrite rule matching, ordering, and shadowing behavior","technology":"netlify","version_scheme":"unknown"},{"constraint":"Declarative [[redirects]] table syntax in netlify.toml","technology":"netlify-toml","version_scheme":"unknown"}]

# Netlify _redirects rules that silently never match

## When this applies

You deployed a site with redirect rules. The deploy log says "All redirect rules deployed without errors." But the URL does not redirect, does not rewrite, or a rule you expected to fire is never reached. The three usual causes are: the rule sits below an earlier rule that already matched, a real file shadows the rule, or the status code means something different from what you assumed.

## Rule processing order: first match wins

The redirects engine reads rules from top to bottom and applies the first rule that matches a request. It does not continue looking for a better match. Rules in the `_redirects` file are always processed before rules in `netlify.toml` — so a catch-all in `_redirects` will swallow rules that exist only in `netlify.toml`.

Dead rule, `_redirects` version:

```
# This fires for /jobs/customer-ninja-rockstar
/jobs/customer-ninja-rockstar  /careers/support-engineer

# This fires for everything else under /jobs
/jobs/*                        /careers/:splat

# DEAD: the rule above already matched this path
/jobs/outdated-job-link        /careers/position-filled
```

Fix: list more specific rules before more general ones. The single most common instance is the SPA catch-all placed too early:

```
# A rule for /jobs/* placed here will never fire
/*             /index.html  200

# SPA catch-all goes LAST
/jobs/*        /careers/:splat
/*             /index.html  200
```

## Splats and placeholders

A splat (`*`) matches anything that follows it. The matched portion is referenced in the target as `:splat`:

```
# _redirects
/news/*  /blog/:splat
```

This turns `/news/2004/01/10/my-story` into `/blog/2004/01/10/my-story`. Two hard limits: a splat can only appear at the end of the path (`/jobs/*` works, `/jobs/*.html` does not), and there is no "exclude this path" syntax — exclusion is done by placing a more specific rule above the splat rule.

A placeholder (`:month`, `:year`, ...) matches one path segment, from one `/` to the next, or a final segment including its file extension:

```
# _redirects
/news/:month/:date/:year/:slug  /blog/:year/:month/:date/:slug
```

`/news/02/12/2004/my-story` becomes `/blog/2004/02/12/my-story`.

Note: edge nodes normalize URLs before rules run, so a rule matches regardless of a trailing slash. A rule from `/blog/typo` also matches `/blog/typo/`. Do not write rules that try to add or remove a trailing slash; they will not behave as written.

netlify.toml equivalents:

```toml
[[redirects]]
  from = "/news/*"
  to = "/blog/:splat"

[[redirects]]
  from = "/news/:month/:date/:year/:slug"
  to = "/blog/:year/:month/:date/:slug"
```

## 200 rewrites vs 301/302 redirects

The status code is the difference between a rewrite and a redirect:

- `301` (the default when no code is given): permanent redirect. The URL in the browser address bar changes to the target.
- `302`: temporary redirect. The URL in the browser address bar also changes.
- `200`: rewrite. The URL in the address bar stays the same; Netlify fetches the target content behind the scenes. This is how SPA catch-alls and same-origin API proxies work:

```
# _redirects
/*      /index.html                  200
/api/*  https://api.example.com/:splat  200
```

A 200 rewrite to an external service is a proxy: the browser never connects to the external host directly.

netlify.toml equivalents:

```toml
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[redirects]]
  from = "/api/*"
  to = "https://api.example.com/:splat"
  status = 200
```

## Forced shadowing with `!`

By default a real file wins over a redirect rule. With only this rule:

```
/*   /index.html   200
```

a request for `/partials/chat.html` still serves the actual file, because the file shadows the rule. This is the preferred behavior for SPAs, but it also silently defeats rules you expected to fire when a same-named file exists.

To force the rule to apply even when a file matches the URL, append `!` to the status code in `_redirects`, or set `force = true` in `netlify.toml`:

```
# _redirects
/app/*          /app/index.html       200!
/best-pets/dogs /best-pets/cats.html  200!
```

```toml
[[redirects]]
  from = "/app/*"
  to = "/app/index.html"
  status = 200
  force = true

[[redirects]]
  from = "/best-pets/dogs"
  to = "/best-pets/cats.html"
  status = 200
  force = true
```

With the forced rule, `/best-pets/dogs` always serves the content of `/best-pets/cats.html`, even if `/best-pets/dogs/index.html` exists. Rule of thumb: use `!` only when you are certain the file should never be served directly.

## Troubleshooting checklist

1. Reorder: put the SPA catch-all (`/* /index.html 200`) last; put specific rules above splat rules.
2. Check for a same-named file: if a file exists at the source path, the rule needs `!` (or `force = true`).
3. Check which file the rule is in: a `_redirects` rule always runs before any `netlify.toml` rule.
4. Check the status code: 301/302 change the address bar, 200 keeps it; the default is 301 when the code is omitted.
5. Check splat placement: `*` must be at the end of the path segment — `/jobs/*.html` never works.
6. Watch for loops: never point a rule's target at a path that matches the rule's own source.


## Supporting basis and limitations

Rule processing order (first match wins; _redirects always processed before netlify.toml): docs.netlify.com/manage/routing/redirects/overview/ 'Rule processing order'. Splats/placeholders (splat at end of segment only; no exclude syntax; placeholder matches one segment or final segment incl. extension; specific rules before general; dead-rule /jobs examples): docs.netlify.com/manage/routing/redirects/redirect-options/ 'Splats', 'Placeholders'. Status codes (default 301; 200 = rewrite with unchanged address bar; 302 temporary; 404 keeps URL): redirect-options 'HTTP status codes'; rewrites-proxies 'When you assign an HTTP status code of 200 to a redirect rule, it becomes a rewrite'. Forced shadowing (! in _redirects, force = true in netlify.toml; files shadow non-forced rules; /app/* /app/index.html 200! example): redirect-options 'Force redirects'; rewrites-proxies 'Shadowing'. SPA catch-all typically listed last: rewrites-proxies 'History pushState and single-page apps'. Proxy example /api/* to https://api.example.com/:splat 200: rewrites-proxies 'Proxy to another service'.

## Change and rationale

New skill: explains the four root causes of Netlify redirect rules that deploy cleanly but never match, with grounded _redirects and netlify.toml examples for rule ordering, splat/placeholder matching, 200 rewrite vs 301/302 redirect semantics, and forced shadowing.

Rules deployed with 'no errors' that do nothing is a recurring, silent failure in Netlify sites. The docs spread the relevant semantics across three pages (processing order, redirect options, rewrites/proxies); this skill compresses them into one diagnostic checklist with real syntax in both config formats.
