Two small text files in your site control how it answers: _redirects moves
or rewrites URLs, and _headers adds HTTP headers. This guide also explains
which file a URL serves, and how your own 404 page is used.
The syntax is the one other common static hosts use, so files you already have usually work unchanged.
Three related settings live in your project's Settings → Routing & SEO,
with no file needed: how page URLs are spelled (trailing slashes and .html),
whether unknown URLs fall back to index.html for a single-page app, and
whether search engines are asked to stay away. They are described in
URL settings below.
Where the files go
Put _redirects and _headers at the top of your published site, next
to index.html:
| How you deploy | Where the files go |
|---|---|
| Import from GitHub | The top of your output directory. With a framework, that usually means the public (or static) folder of your project, which the build copies into the output. |
| Upload a zip | The top of the zip, next to index.html. |
| CLI | The top of the folder you deploy. |
Files with these names in subfolders are ignored. Neither file is served to
visitors: https://example.com/_redirects is a 404. Paths under .zeloxa/
are reserved for Zeloxa and are not published.
What you'll see: in the deployment log, a line such as "Site config: 3 redirect rules, 2 header rules." The files are read once, when you deploy. Change them and deploy again for changes to take effect.
_redirects
One rule per line: where from, where to, and an optional status code.
# from to status
/old-page /new-page 301
/blog/* /news/:splat 301
/team/:name /people/:name 301
/shop https://shop.example.com 302
/app/* /app/index.html 200Status codes
| Status | What it does |
|---|---|
301 | Permanent redirect. Use it when a page has moved for good. |
302 | Temporary redirect. This is the default when you leave the status out. |
303, 307, 308 | Also redirects. 308 is the permanent one that keeps the request method. |
200 | A rewrite: the visitor stays on the URL they asked for, and is shown the destination's page. |
A ! after the status (301!, 200!) forces the rule: it applies even
when a file exists at the URL. Without it, a file wins (see Matching).
Matching
*matches the rest of the path, including slashes. Use it in the destination as:splat./blog/*also matches/blogitself.:name, as a whole path segment, matches one segment. Use it in the destination as:name.- Matching ignores the query string and a trailing slash, so
/aboutand/about/are the same. It is case-sensitive:/Aboutdoes not match/about. - The visitor's query string is kept:
/old-page?ref=mailgoes to/new-page?ref=mail. - Rules are checked from top to bottom. The first match wins.
- A rule only applies when no file exists at the URL. If
/aboutwould serveabout.html, a/about /about-us 301rule is skipped. To move a page that is still in your build, force the rule:/about /about-us 301!. - A rule that would send visitors to the URL they are already on is skipped, so you cannot create a loop by accident.
Destinations
- A path on your site, starting with
/. - Or a full address starting with
https://.
A 200 rewrite must point at a path on your own site. It cannot fetch
another website.
What is refused
A line that cannot be used is skipped, with a warning in the deployment log. The deploy itself still succeeds. Lines are refused when:
- the status is not one of
200,301,302,303,307or308; - the line has more than three parts (conditions such as
Country=orRole=, and matching on query parameters, are not supported); - the source does not start with
/, contains?or#, or has more than one*; :splatis written in the source (use*), or one:nameappears twice;- the destination starts with
http://,//or anything other than/orhttps://, contains spaces, or contains a user name or password; - a placeholder is used in another website's address
(
https://:city.example.com/); - the destination uses a
:namethe source does not define; - a
200rewrite has a query string or#in its destination.
_headers
A path pattern on its own line, then the headers for it, each on an indented line:
/*
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=()
/assets/*
Cache-Control: public, max-age=31536000, immutable
/downloads/*
Content-Disposition: attachment- Patterns use the same
*and:namerules as_redirects. - Every matching pattern applies, from top to bottom. If two patterns set the same header, the values are joined with a comma.
:nameand:splatin a value are filled in from the pattern.! Header-Nameon an indented line removes a header.- Header rules apply to every page and file served, including your 404 page. They do not apply to redirects.
These headers cannot be set or removed, and are refused with a warning:
Content-Length, Content-Encoding, Content-Range, Transfer-Encoding,
Connection, Keep-Alive, TE, Trailer, Upgrade, Host, Set-Cookie,
Location (use _redirects), ETag, Date, Age, Server, and any name
starting with X-Zeloxa-, CF- or Proxy-.
Zeloxa always sends X-Content-Type-Options: nosniff. Its default
Referrer-Policy: strict-origin-when-cross-origin can be replaced or removed
(! Referrer-Policy). Security headers you
turn on under Settings → Security headers (such as HSTS) take priority
over the same header in _headers. On a password-protected site,
Cache-Control is always private, no-store.
Lines are refused when a pattern line contains a space (header lines must be indented), a header line comes before any pattern, a header name is not valid, or a value contains control characters. A pattern with no headers under it is skipped too.
Examples
Move pages
/about-us /about 301
/blog/2023/* /blog/:splat 301
/products/:id /shop/:id 301Send the old domain's paths to a new site
/* https://www.new-example.com/:splat 301Single-page apps (React, Vue and others)
A single-page app needs every unknown URL to load index.html, so its own
router can show the right screen.
You usually do not need a rule. Zeloxa recognises a single-page app
when you deploy it and answers every unknown URL without a file extension with
index.html (see SPA fallback). To be sure, set SPA
fallback to On under Settings → Routing & SEO.
The rule other hosts use works here too:
/* /index.html 200Files that exist (your scripts, styles and images) are still served, because
a rule only applies where there is no file. Do not write it as 200!: a
forced rule would answer those files with index.html as well.
Security headers
/*
X-Frame-Options: SAMEORIGIN
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-Policy: same-originHSTS and a Content Security Policy can also be switched on without a file, under Settings → Security headers.
Caching
Zeloxa picks sensible caching for you:
| File | Default Cache-Control |
|---|---|
.html, .htm, .json, .xml, .txt, .webmanifest | public, max-age=0, must-revalidate (always checked, so a deploy shows at once) |
Names with a content hash, such as app.4f2a9c1b.js, logo-3e8a51f0.png or index-B4kP_x9Q.js, and everything under _astro/ and _next/static/ | public, max-age=31536000, immutable (kept for a year) |
| Everything else | public, max-age=0, s-maxage=86400, stale-while-revalidate=604800 |
Override it with _headers when you know better:
/fonts/*
Cache-Control: public, max-age=31536000, immutable
/feed.xml
Cache-Control: public, max-age=300Warnings in the deployment log
Problems in either file never fail a deploy. Each skipped line is reported as
a warning in the deployment's log (on the Deployments tab choose View
logs, or npx zeloxa logs from the CLI):
Site config: 2 redirect rules, 1 header rule.
Warning: _redirects line 4: destination must start with / or https://; skipped.
Warning: _headers line 7: Set-Cookie cannot be set from _headers; skipped.Up to 25 warnings are listed, then "…and N more warnings."
Limits
| Limit | Value |
|---|---|
| Redirect rules | 2,000 (the rest are skipped, with a warning) |
| Header path patterns | 100 |
| Headers under one pattern | 50 |
| Line length, either file | 2,000 characters |
| Size of either file | 512 KB (a larger file is ignored, with a warning) |
Which file a URL serves
For a static site, Zeloxa looks for these files, in order, and serves the first one that exists:
| The visitor asks for | Zeloxa tries |
|---|---|
/ | index.html |
/about | about.html, then about/index.html |
/about/ | about/index.html |
/about.html | about.html |
/assets/app.js | assets/app.js |
- A path whose last part has an extension (
.js,.png,.pdf, or any.followed by letters or digits) is looked up exactly as written. - With the default Trailing slash: Auto setting,
/about/redirects to/about, and/about.htmlis served as it is. Choose Always or Never under Settings → Routing & SEO for one canonical form (see URL settings). - A
_redirectsrule that matches is used only when the lookup above finds no file, unless the rule is forced with!.
If nothing matches, see the next section.
Your 404 page
When no file matches (and no _redirects rule applies):
- With SPA fallback: On, a URL without a file extension is answered with
index.htmland status200, even if you ship a404.html. - Otherwise, if your site has a
404.htmlat the top, it is shown, with status404. This applies to every missing URL, pages and files alike. Only the top-level404.htmlis used. - Otherwise, with SPA fallback on (or Auto for a build that looks like a
single-page app), a URL without a file extension gets
index.htmlwith status200. - Otherwise, a plain "Page not found." page is shown, with status
404.
A missing /robots.txt is the exception: see Search engines.
URL settings
These are per project, under Settings → Routing & SEO, and also in the
API (GET and PATCH /sites/{siteId}/settings). Changes take effect within
30 seconds, without a new deploy.
Trailing slash
| Setting | What visitors get |
|---|---|
| Auto (default) | /about/ redirects to /about. /about serves about.html or about/index.html. /about.html is served as it is. |
| Always | Page URLs end in a slash. /about, /about.html and /about/index.html redirect to /about/. |
| Never | Page URLs have no slash and no .html. /about/, /about.html and /about/index.html redirect to /about. |
Redirects are 308 and keep the query string. With Always or Never,
/index.html redirects to /, and a page is only redirected when its
canonical URL serves the same file. URLs of other files (/app.js) and
anything under /api/ are never changed. Always suits sites built as
folders (about/index.html); a page built as about.html that uses relative
links can break under it.
SPA fallback
What a URL without a file extension that matches no file gets:
| Setting | Behaviour |
|---|---|
| Auto (default) | Decided when you deploy: on if the build looks like a single-page app, off otherwise. |
| On | index.html with status 200, for your app's router. |
| Off | Your 404.html (or a plain not-found page) with status 404. |
A build looks like a single-page app when it has no 404.html, its
index.html loads a script file, and it has at most two other HTML files.
A 200.html at the top also marks one. Builds deployed before this setting
existed keep working as they always did, as single-page apps. The deployment
log says which way a build was read: "Looks like a single-page app" or "Looks
like a multi-page site".
Search engines
- Discourage search engines sends
X-Robots-Tag: noindex, nofollowwith every response and serves arobots.txtthat disallows everything. - If your build has no
robots.txt, Zeloxa serves one:User-agent: *andAllow: /, plus aSitemap:line when your build has asitemap.xml. On preview addresses, and with Discourage search engines on, it saysDisallow: /instead. Your ownrobots.txtalways wins and is served exactly as you wrote it. - Every address of your site redirects plain HTTP to HTTPS.
- Once a custom domain is verified, it becomes the project's primary domain and the project's own address redirects to it (choose another, or keep both live, on the Domains tab). Preview addresses are never redirected.
Full-stack apps
Builds that include server code (a _worker.js at the top) work
differently. Files are only served when they exist at exactly the requested
path; everything else goes to your app. _redirects rules and the 404 rules
above are not applied, because your app's router owns its URLs. _headers
rules still apply to the files that are served.
Related
- Deploy your first site
- Redirect www to your root domain
is a domain setting, not a
_redirectsrule. - The same rules in reference form: Redirects and headers in the API reference.
