The 404 page and the SPA fallback
When a visitor opens a URL that is not in your build, Layero has to answer with something. There are two correct behaviours — and they are opposites:
- A multi-page static site (Next.js
output: 'export', Astro, Hugo, Eleventy, Jekyll and so on) usually puts a404.htmlfile in the build and expects an unknown address to return exactly that, with HTTP status 404. That is how search engines — Yandex, Google — learn the page is gone. - A SPA with client-side routing (React, Vue, Angular, Solid without
prerendering) wants the opposite: any address should return
index.htmlwith status 200, so the app loads and its own router shows the right screen (or its own not-found state).
The default behaviour
Layero picks automatically, based on what is in the build:
Is there a 404.html at the build root? | An unknown URL returns |
|---|---|
| Yes | 404.html with status 404 |
| No | index.html with status 200 (the SPA fallback) |
Most projects need to do nothing: Next export, Astro, Hugo and similar
generators write 404.html themselves, and it starts being served with the right
status. Vite and CRA apps do not create 404.html, so they keep working as SPAs.
The 404.html file itself is always reachable at its direct address
(/404.html) with status 200 — only unknown URLs get the 404.
Overriding through _redirects
If the automatic choice does not suit you, put a _redirects file (Netlify
syntax) at the build root with a catch-all rule:
# Force the SPA fallback even when 404.html is present:
/* /index.html 200
# Or the other way round — explicitly turn on your own 404 page:
/* /404.html 404
/* /index.html 200— always serveindex.htmlwith status 200. This is for apps with client-side routing that generate a404.htmlanyway./* /404.html 404— serve the given page with status 404. The path can be anything (/not-found.htmland so on); the file has to exist in the build.
The rule must come last in _redirects — ordinary redirects (301/302)
in the same file keep working as before.
Checking it
curl -I https://<your-project-address>/a-page-that-does-not-exist
# HTTP/2 404 ← expected for a static site with a 404.html
# HTTP/2 200 ← expected for a SPA
The change takes effect with the next deploy — Layero determines the 404 page at build time.