Tutorial 4 of 21 · for anyone who has hand-tuned a cache before

Let FSR cache it for you

You'll make pages fast without writing a cache. FSR works out which routes read nothing of the request, renders those once and answers them from a file. For the ones that do read the request, it caches the expensive half and leaves the rest live. You don't tag anything, invalidate anything or reach for a key.

Before you start

Straight from crates.io. No Node, 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.

Turn on the render memo

One section. Without it nothing is cached at all.

toml
[cache]
capacity = 1000
ttl = "1m"

That's the in-memory memo for rendered documents. It's the boring part.

Prerender the pages that never change

A route with no parameter, whose every loader is lowered and reads nothing of the request, renders the same for everybody. FSR already knows which those are. Ask it:

$ fsr dev myapp/app
prerender /about                 dist/prerender
          /pricing               dist/prerender
warm      layout.promo           not warmed

Now point the config at somewhere to put them and render them:

toml
[server]
prerender = "dist/prerender"
$ fsr prerender myapp/app
/about                 dist/prerender/about/index.html
/pricing               dist/prerender/pricing/index.html

From then on a GET for those paths is answered from the file with an x-sf-prerendered: 1 header. Your session cookie and your middleware still run. One file is written per locale, so /about and fr_FR/about/index.html sit side by side and the request's locale picks.

What makes a route dynamic

Reading the request. A loader that touches a parameter, the query, the session or the clock keeps its route live. So does a Rust source, because the host can't see what a Rust function reads.

There is one useful exception. A route whose only request read is the identity gets prerendered for the anonymous public. The report says so:

prerender /posts                 dist/prerender for anonymous visitors

Signed-out visitors get the file. Signed-in ones are rendered live and memoized under their own subject. That's also how a preview works with no draft flag and no secret: the editor signs in, their loader's call carries their token, the public keeps the file.

Cache just the slow part

Here is the part that usually needs hand-tuning. One layout is enough to make a whole subtree dynamic. A console whose header shows who you're watching means every page under it reads the request, however fixed the page itself is. The route verdict is correct and too coarse to be useful, because the cost was never the render, it was the loader.

So FSR classifies per source as well as per route. fsr prerender runs every source under warm once with no request behind it, then writes the results beside the documents.

$ fsr prerender console/app
warm      layout.promo           2 loads memoized
          widths

At boot the host reads that file. A request reaching one of those sources takes the loaded value instead of running the loader. No service call, no interpreter. The console's header is still per session; the fixed layout above it costs nothing.

Why it can't go stale

A request never writes to the memo. Only a build fills it. A source the build did not reach costs a load every time, which is slow rather than wrong. Nothing grows without bound. A rebuild is what refreshes it.

That's the whole invalidation story. No key to build, no tag to attach, no revalidate to guess at.

No cache keys to maintain

No cache keys, no per-route TTL, no tag-based invalidation. No list of what is safe to cache, because the build works that out from what each loader reads.

Next up: 050. Find out what won't compile, which is how FSR tells you a loader just became dynamic.

Built with SnapFire FSR. Pure Rust runtime, zero Node.js on the server.

Proudly Created by Excerion Sun LLC