Prerender a blog at build time
You'll build a three-post blog where every post is a file on disk before the first request arrives. A post that isn't in the set still renders live, so a new post is served before the next prerender picks it up.
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.
Which routes prerender on their own#
A route renders once for everyone when its loader reads nothing about the request: no query, no session, no identity, no clock. The host works that out from the lowered loader. fsr prerender writes those routes to files and the boot report lists them.
A route with a parameter doesn't qualify. /post/{slug} renders the same for every request given a slug, but nothing on the server knows which slugs exist. So it renders per request, which suits most routes but not a blog.
Say what the slugs are#
Start an app and give it some posts:
app/src/posts.ts:
;
;Now the post route. The loader is an ordinary one and the new export is paths on the last line:
;
;
;
;paths returns one object per path, with a key per parameter in the pattern. It runs at build rather than per request, so it may call a service, which is what you want when the slugs come out of a CMS instead of a constant. It may not read the request; the build refuses it if it tries.
The page itself is ordinary:
;
Write the files#
Point the config at a directory first, in config/app.toml:
[]
= "dist/prerender"That path is relative to the app directory, not the project root. Then build and prerender:
The grep hides one row per fsr client module, which the run also writes under static/js/fsr/ so the directory holds everything the pages load.
Each path gets two files. index.html is the document a cold visitor gets. index.payload is what a client navigation asks for, so clicking through to a post from the index is also served from disk. prerender.build records which build the files were rendered from. A host built from different sources doesn't read them, so a stale prerender is never served after a deploy that forgot to run this step.
prerendered.json is the list of what this run wrote, the pages and the client modules. The next run deletes everything in that list before it writes again. Delete a post from POSTS and the next run removes its file.
Read the boot report#
per paths is how you tell the two cases apart. The index qualified on its own because its loader reads nothing. The post route qualified because you said what the slugs are.
Check it#
fsr serve reads config/development.toml, which turns dev on. In development every page renders live, so an edit shows on reload rather than waiting for the next prerender. Ask for the prerendered version with ?__prerendered:
preview means the host rendered the page the way fsr prerender does, anonymously and with the head a written file carries, without reading the directory. Without the query the same request has no x-sf-prerendered header at all.
In production the files are what answers. Stop the server and start it the way a deployment does, with RELEASE_ENV=production and a session key, since config/production.toml refuses its placeholder:
x-sf-prerendered: 1 means the host answered from the file without running a loader or walking the plan.
Now ask for one that isn't there:
No x-sf-prerendered header, because that one was rendered live: the loader ran, found nothing and raised fail("not_found", ...). The status follows the failure, so crawlers and caches see a real 404. The message comes through too, with the slug interpolated:
there is no post nope
The kind is a string literal because the build matches on it, but the message is any expression and it's evaluated only if the guard actually fires.
Posts added between deploys#
The set only decides which paths get a file. A path in it is served from that file; a path outside it renders the way it always did. So a post added to the database between deploys is live immediately, just without the file, until the next fsr prerender picks it up.
Reading the clock also stops a route prerendering.
nowis a request read like any other, so the route drops off theprerenderlist at the next build whateverpathssays. A post page that shows "posted 3 days ago" reads the clock, so it won't prerender.
Try it#
Take paths out and build again. The /post/{slug} row leaves the boot report's prerender list altogether, not just its per paths note. The / row says its files were written by another build until you prerender again. A post asked for with ?__prerendered now answers 404, since there's no prerendered version of it to preview. Put paths back.
Then delete the middle post from POSTS and run fsr prerender a second time. Read prerendered.json before and after: the two post/the-plan-file/ files are gone from the directory, not just from the list.
Last, leave paths in place and make the post loader read query as well. The route still drops off the list, because a query is part of the request and no file answers every query. paths lists the slugs. It doesn't exempt the route from any other request read.