carlos-static-redirects
What
Static sites (--kind static) can ship a _redirects file at the top of the site directory. It uses the Netlify / Cloudflare Pages format: one source destination [status] rule per line.
- Sources: a path, or an
http(s)://host/...URL that matches only that host (forwww→ apex).:namecaptures one segment. A final*captures the rest of the path as:splatand keeps a trailing slash. - Statuses: 301 (the default), 302, 303, 307 and 308 redirect. 200 serves another file from the same tree at the requested URL (SPA fallback). 404 serves a file as the not-found page.
- Precedence: a file at the requested path wins, unless the status ends in
!. The first matching rule applies. - Query strings: the request's query carries over unless the destination has its own.
Why
Until now a static site could not send a redirect of its own. The only redirect was the hard-coded /index.html → ./. The workarounds were meta-refresh pages (a 200, not a redirect) or running the site as an app instance. _redirects is the closest thing to a standard: Netlify and Cloudflare read it, and many site generators write it. The file ships inside site.tar.gz, so the rules are versioned and roll back with the content.
How
internal/edge/redirects.go(new) has the parser and matcher.internal/edge/static.go: -PackDirrefuses a root_redirectsthat has any line the edge cannot use, and names each such line.carlos shipandcarlos deploytherefore fail at ship time instead of silently skipping a rule. - The edge parses the file once per version swap. It keeps the rules inservedTreewith that version's dir and version, so a request never pairs one version's files with another version's rules. -serveTreekeeps its own lookup and runs it once more for a rewrite destination. A newserveFileserves the file, either as the 200 it always was or under a rule's 404 status. A site without_redirectstakes exactly the old path.- Safety: - The edge never serves
_redirects. - Destinations starting//or/\are refused. - Captures are percent-encoded into the Location. - Path Locations are cleaned the wayhttp.Redirectcleans them, so an empty capture cannot make one protocol-relative. The loop guard compares the Location that is actually sent. - A redirect to the URL being requested is skipped. - Rewrite targets are cleaned inside the tree. - Not supported: Netlify conditions (
Country=etc.), proxying to other hosts, andnetlify.toml. - Limits: 2000 rules, 256 KiB, 2048 characters per line.
- Docs: a new subsection, "Redirects for a static site", under
carlos deployindocs/site/cli.md.
Testing and review
internal/edge/redirects_test.go: - Parser tests: the forms that are accepted, about 25 lines that must be refused, keeping the good lines around a bad one, the limits, and a BOM. -PackDirrefusing a bad file. - End-to-end handler tests that ship a real site. They cover shadowing, forcing, splats and placeholders, query and fragment handling, host rules, the loop guard, SPA fallback with a working ETag and 304, the custom 404 page including HEAD, and/_redirectsnot being served.go vetandgofmtare clean. Theinternal/edgeandinternal/docsitetests pass locally.- An Opus review found no security issues and four bugs, all fixed with regression tests: - The loop guard missed a
/x/* /x/:splat/ 301!loop. - A rewrite was cut at a?or#that came from a decoded capture. - Percent-encoded sources passed ship but never matched. They are now decoded. - A UTF-8 BOM made ship refuse the file. internal/provisionpasses, census included.- Amadan CI passed on e6e3d2eb; rebased over main as ff350c27 (CI pending at the time of writing).
Root read census
The change touches four units in the root read census: PackDir, staticSite.serveTree, the new staticSite.serveFile and the new staticSite.loadRedirects. Overlay internal/provision/testdata/plan14-root-reads-static-redirects.tsv loads after db-region-forward-copy, and the seal moves to 65857c29…d831. Every new read stays inside the extracted, digest-verified tree. The ruling is asked of the census owner on #280; the rows carry PROPOSED-static-redirects until then.
The first two CI runs failed on this census. The internal/console failure in the first run came on top of that.
Open questions for a reviewer
- Should ship refuse unsupported lines (current behaviour) or warn and drop them? Refusing is stricter than Netlify and Cloudflare, which ignore bad lines.
- A
404.htmlconvention without any rule, as Netlify, Cloudflare and GitHub Pages do, would be a natural follow-up. It is not in this branch.
Static sites honour a Netlify/Cloudflare-style _redirects file: redirects, SPA rewrites, custom 404s. Census overlay proposed on #280.
Discussions 1 open
Tasks
- No tasks yet.
Activity
- james@keymail.dev replied to Review: carlos-static-redirects
- james@keymail.dev opened Review: carlos-static-redirects