---
title: "Cache loaders and renders"
audience: "anyone who has hand-tuned a cache before"
description: "You'll make pages fast by letting FSR do the caching. FSR works out which routes read nothing of the request, renders those once and answers them from a…"
snippet_caption: "config/app.toml"
snippet: "[cache]\ncapacity = 1000\nttl = \"1m\""
---

import { Install } from "@src/ui/Install";

You'll make pages fast by letting FSR do the caching. 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.

<Install captured="0.x" />

## Turn on the render memo

Without this section nothing is cached at all.

```toml title="config/app.toml"
[cache]
capacity = 1000
ttl = "1m"
```

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

## 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:

```shell-session
$ fsr dev myapp/app --verbose
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 title="config/app.toml" {2}
[server]
prerender = "dist/prerender"
```

```shell-session
$ 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

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.

A route whose only request read is the identity is an exception: it gets prerendered for anonymous visitors. The `--verbose` report says so:

```shell-session
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. Previews work the same way. The editor signs in and their loader's call carries their token, while the public still gets the file.

## Cache just the slow part

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. So the route can't be prerendered, even though most of the cost is in 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.

```shell-session
$ 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. The console's header is still rendered per session.

## When the memo refreshes

Only a build fills the memo. A request never writes to it, so it can't grow without bound. A source the build did not reach runs its loader on every request, which is slower but still correct. Rebuild to refresh the memo.

Next up: [120. Find out what won't compile](https://snapfirers.com/fsr/learn/120-find-out-what-wont-compile), which is how FSR tells you a loader just became dynamic.
