Fix SPA 404s and Canonical URLs with Netlify Redirect Rules
Netlify edge redirect rules can serve index.html for unknown SPA paths and enforce canonical URLs, but rule ordering and status-code choice determine whether your app works.
17 Jun 2026, 12:31 UTC

You shipped a single-page application (SPA) using history-mode routing. Clicking navigation works perfectly, but when a user refreshes on /account/settings or pastes a deep link into the address bar, Netlify returns a 404. The edge server looked for a file at that exact path and found nothing. Your JavaScript router never loaded because the request never reached it.
The fix does not require a custom server. Netlify evaluates redirect and rewrite rules at the edge, after your deploy finishes but before it serves files from the publish directory. A 200 rewrite can return your index.html for unknown paths while leaving the browser URL unchanged. Separate 301 or 302 rules can enforce canonical URLs. The catch is that rules are matched top-to-bottom and the first match wins, so ordering determines whether your app works or breaks.
Rewrites keep the URL; redirects change it
Netlify supports two behaviors that look similar in configuration but produce very different browser experiences.
- A rewrite with status
200serves the content of another file—typicallyindex.html—without altering the address bar. The user still sees/account/settings, and your client-side router takes over. - A redirect with status
301(permanent) or302(temporary) tells the browser to request a different URL. The address bar changes. This is the right tool for moving content or collapsing duplicate URLs.
For SPA fallback, you want the rewrite. Forcing a redirect to /index.html would expose the raw path in the URL and break deep linking.
Rule ordering: specific rules must come first
Because Netlify stops at the first matching rule, a catch-all SPA rewrite placed at the top will swallow every request, including API calls and static assets. Put narrow rules above the broad fallback.
For example, if your app proxies /api/* to a backend service, that rule must appear before the SPA catch-all. Otherwise, requests to /api/users would receive index.html instead of JSON, and your frontend would fail silently.
Canonical URLs and the HTTPS trap
Many teams add rules to force http:// to https://. On Netlify, this is usually unnecessary and can cause redirect loops. Netlify provisions TLS certificates and serves HTTPS by default for both custom domains and .netlify.app subdomains. A rule that rewrites every HTTP request to HTTPS may conflict with the platform's own edge handling, especially when DNS or proxy settings are involved.
Canonicalization that is useful is collapsing duplicate URLs—whether that means redirecting www to a non-www apex domain or sending moved pages to their new homes. Use a 301 redirect so search engines and browsers update the canonical URL. Because host-based matching depends on your domain configuration, verify the exact rule syntax in Netlify's current documentation before deploying.
A worked example in _redirects
Netlify reads rules from either a _redirects file in your publish directory or a netlify.toml file in your repository root. The _redirects file must be included in the build output; if your build tool cleans the publish directory, make sure the file is copied there. The netlify.toml approach keeps rules version-controlled alongside your code.
Suppose your publish directory is dist and you use _redirects. Create the file with these rules, ordered from specific to general:
# Proxy API requests to an external service
/api/* https://api.example.com/:splat 200
# Redirect a moved page permanently
/old-pricing /pricing 301
# SPA history-mode fallback (must be last)
/* /index.html 200
If you prefer netlify.toml, the equivalent configuration in your repository root looks like this:
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
[[redirects]]
from = "/old-pricing"
to = "/pricing"
status = 301
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Verifying in a deploy preview
Before merging to production, push the branch and let Netlify build a deploy preview. Deploy previews use unique URLs per pull request, so you can test edge behavior without affecting live traffic.
From your terminal, request a deep path that does not exist as a file:
curl -I https://deploy-preview-123--your-site.netlify.app/account/settings
Check that the response status is 200 and the body returned matches your index.html content. If you see 404, the catch-all rule is missing, misordered, or the _redirects file did not land in the publish directory.
Next, test the canonical redirect by requesting the moved path:
curl -I https://deploy-preview-123--your-site.netlify.app/old-pricing
Inspect the Location header to confirm it points to /pricing. A 301 or 302 status with the correct target means the rule matched.
Finally, compare behavior across contexts. Netlify supports context-specific configuration in netlify.toml using [context.deploy-preview] or [context.branch-deploy] blocks. Rules or headers that differ between preview and production can hide bugs until after merge.
Limitations and trade-offs
Redirect rules are powerful but not limitless.
| Concern | What to watch for |
|---|---|
| Rule count and performance | Very large rule sets can affect edge evaluation time and may approach account-level limits. Check Netlify's current documentation for caps. |
| Cache behavior | Broad proxy rewrites (status 200 to an external URL) can bypass CDN caching for matched paths, increasing origin load. |
| Literal matching | Query strings and trailing slashes are matched exactly unless you use splat (*) or placeholder (:splat) syntax. Test the exact URL shapes users will request. |
| Security | A public rewrite exposes whatever file exists at the destination. Redirect rules do not enforce authentication or authorization. |
| Build assumptions | This approach assumes a static build with a publish directory. Frameworks that render only on demand may require different configuration. |
Actionable checklist
- Choose one source of truth:
_redirectsin the publish directory ornetlify.tomlin the repository root. - Order rules from most specific to least specific. API proxies, asset exceptions, and canonical redirects go above the SPA catch-all.
- Use
200for SPA fallback so the URL stays intact. Use301or302only when the browser URL should change. - Do not add manual HTTP-to-HTTPS rules unless you have a specific non-standard setup; Netlify handles TLS termination at the edge.
- Validate in a deploy preview with
curl -Ibefore merging. Confirm status codes andLocationheaders. - Review Netlify's current documentation for supported status codes and redirect rule limits before rolling out hundreds of rules.
Redirect rules are configuration, not code, which makes them easy to change and easy to get wrong. A single misordered catch-all can take down an entire API integration. Test the exact paths your users will hit, verify in preview, and keep the rule set as small as your routing requires.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.