Problem
asws has three ways to answer a request that matches no file, and none of them can retire a single indexed URL correctly.
Today NoRoute offers:
SPA_FALLBACK=true — serve index.html with 200 for every unmatched path
NOT_FOUND_REDIRECT=true — send every unmatched path to one NOT_FOUND_REDIRECT_PATH, with 307
NOT_FOUND_FILE — serve a custom body with 404
A prerendered static site that renames a page has a narrower need than any of those. One specific old path should answer 301 to one specific new path. Everything else should keep 404ing.
Each existing mode fails that case in its own way:
- SPA fallback turns every typo and every dead link into a 200. Search engines call this a soft 404 and it is worse than the problem it solves: the site loses the ability to tell a crawler that anything is genuinely gone.
- Not-found redirect is all-or-nothing and points everything at one destination, so it cannot express "this page moved there."
- Custom 404 file is honest but drops the link equity. An indexed URL returning 404 loses whatever ranking signal accumulated against it, and a client-side router redirect after the fact does not recover it, because the status code is what the crawler acts on.
The workaround people reach for is to put a reverse proxy in front of asws purely to hold a handful of redirect rules, which is a lot of moving parts for what is a static lookup table.
Proposal
A declarative redirect map, consulted in NoRoute before the SPA fallback and the 404 handling.
REDIRECTS_FILE="./www/_redirects" # default; disabled when the file is absent
Plain-text format, one rule per line, matching the convention Netlify and Cloudflare Pages already established so existing build tooling can emit it unchanged:
/old/path /new/path 301
/retired / 308
/legacy /current # status omitted, defaults to 301
Behaviour:
- Exact path match is enough for the motivating case and should land first. Prefix and splat matching are a reasonable follow-up, but a v1 that only does exact matches already solves this.
- Status code per rule, defaulting to
301. 302, 307, and 308 should all be expressible; a permanent rename wants 301 or 308, and a temporary one wants 302 or 307.
- Query strings preserved and appended to the target, so
/old?utm_source=x reaches /new?utm_source=x.
- Loaded once at startup and held in a map. These files are small and static; watching the file for changes is not worth the complexity.
- Malformed lines logged and skipped rather than fatal, so one bad line does not take the server down. Blank lines and
# comments ignored.
- Precedence: a real file on disk always wins, since
NoRoute only fires when nothing matched. Within NoRoute, redirect map first, then SPA_FALLBACK, then NOT_FOUND_REDIRECT, then NOT_FOUND_FILE. That ordering lets a site combine an honest 404 page with a handful of real redirects, which is the combination that is impossible today.
Why this belongs in asws
The current feature set already implies it. asws emits a 301 today for trailing-slash normalisation on static paths, so permanent redirects are established behaviour rather than a new concern. What is missing is a way to declare them. A static server that prerenders routes will inevitably rename some of them, and the redirect map is the piece that makes a rename non-destructive.
Acceptance criteria
- A path listed in the redirects file responds with the configured status and a
Location header pointing at the target
- A path not listed keeps its current behaviour exactly, including the 404 status from
NOT_FOUND_FILE
- Query strings survive the redirect
- An absent redirects file leaves all current behaviour unchanged, so this is additive and existing deployments need no config change
- A malformed line is logged and skipped, and the remaining rules still load
- README documents the file format, the default path, and the precedence order against the existing modes
Problem
asws has three ways to answer a request that matches no file, and none of them can retire a single indexed URL correctly.
Today
NoRouteoffers:SPA_FALLBACK=true— serveindex.htmlwith 200 for every unmatched pathNOT_FOUND_REDIRECT=true— send every unmatched path to oneNOT_FOUND_REDIRECT_PATH, with 307NOT_FOUND_FILE— serve a custom body with 404A prerendered static site that renames a page has a narrower need than any of those. One specific old path should answer
301to one specific new path. Everything else should keep 404ing.Each existing mode fails that case in its own way:
The workaround people reach for is to put a reverse proxy in front of asws purely to hold a handful of redirect rules, which is a lot of moving parts for what is a static lookup table.
Proposal
A declarative redirect map, consulted in
NoRoutebefore the SPA fallback and the 404 handling.Plain-text format, one rule per line, matching the convention Netlify and Cloudflare Pages already established so existing build tooling can emit it unchanged:
Behaviour:
301.302,307, and308should all be expressible; a permanent rename wants 301 or 308, and a temporary one wants 302 or 307./old?utm_source=xreaches/new?utm_source=x.#comments ignored.NoRouteonly fires when nothing matched. WithinNoRoute, redirect map first, thenSPA_FALLBACK, thenNOT_FOUND_REDIRECT, thenNOT_FOUND_FILE. That ordering lets a site combine an honest 404 page with a handful of real redirects, which is the combination that is impossible today.Why this belongs in asws
The current feature set already implies it. asws emits a 301 today for trailing-slash normalisation on static paths, so permanent redirects are established behaviour rather than a new concern. What is missing is a way to declare them. A static server that prerenders routes will inevitably rename some of them, and the redirect map is the piece that makes a rename non-destructive.
Acceptance criteria
Locationheader pointing at the targetNOT_FOUND_FILE