Find broken links
A page can render correctly while some of its requests fail. You'll learn what the deploy tree actually serves, why development never shows you the difference and how to catch a failed request that leaves no mark on the screen.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
A page that renders with failed requests#
On one live site, eleven module requests per page load answered 404 for a week. The browser had already loaded those modules under a different URL, so every page rendered correctly and the only sign was errors in the console. Each of those 404s was still a defect.
What the tree holds#
serve/static/js/fsr serves /static/js/fsr
serve/static/js/app serves /static/js/app
serve/static/js/vendor serves /static/js/vendor
app/generated/plan.sexp read by the host
config read by the host
117 files, 4013390 bytes under dist
place beside it: the binary, the logging configuration
The command reads the host's own static roots from the configuration the same way the host does at boot, so a web server answers off disk the same URLs the host answered under fsr dev. Adding a [[static]] entry changes the deployment.
The binary and the logging configuration are left out deliberately, because they differ per deployment. The command says so on the last line.
Development and production#
Under fsr dev the host serves every static root itself, out of the source tree. In production a web server serves serve/ off disk and everything else proxies through. Those are different code paths, so development only exercises one of them.
A file the host would have served from memory in development can be missing from the tree. nginx then answers =404 and the host never sees the request at all. Nothing appears in your logs or your traces and the page still renders, because whatever needed that file had already got it some other way.
Run the production shape locally#
Point a static-first stand-in at serve/ with everything else proxied to the process. This reproduces the deployment on your laptop. Save it as stand_in.py beside the bundle:
=
=
return None
=
=
=
return
=
return
=
=
=
=
=
=
=
=
=
Start the host from the bundle on port 3000, run python3 stand_in.py dist/serve and browse http://127.0.0.1:8080.
The print puts every path the tree does not hold in one log you can read at the end of a sweep, rather than in a console you have to be watching.
Resolve the path to an absolute one before the containment check, otherwise every request 404s. Put
.mjsin your MIME table, otherwise the browser refuses modules for a reason that reads like a bundler problem.
Check every route#
Read the console and the network log, since a screenshot looks the same whether requests failed or not.
check navigates, waits for load, lets async errors settle and reports console errors, uncaught exceptions and failed network requests, plus a screenshot. Run it over every route rather than the one you changed, because the failure above was on every page and looking at one page never showed it.
A route passes when it reports pass: true with an empty errors array. Treat a 4xx in that array as a defect even when the screenshot looks right.
Sign off a deploy#
Run check on every route in the production shape before you call a deploy working. A green build, a page that renders and a passing test suite don't look at requests, so none of them shows a 404 like the ones above.
Next up: 450. Set a content security policy.