---
title: "Render at build time and serve from memory · Agent Wranglers"
url: https://agentwranglers.se/playbook/website/site-architecture/render-at-build-time-serve-from-memory
updated: 2026-10-20
licence: "All rights reserved"
---

# Render at build time and serve from memory

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Site architecture](https://agentwranglers.se/playbook/website/site-architecture)
- Rule: Do all templating at build time. Render each page once, compress it once, and keep the bytes, ETag and security policy in memory.
- How: On start, or on first request, render each page and work out its CSP hashes and ETag. Compress it only in the encoding a client asks for, and cache those bytes. Key the cache by path or a short hash, never by the page text itself. Keep page sources on disk, not in memory, so a large site fits a small server. Insert content into the template literally, as the [literal replacement rule](https://agentwranglers.se/playbook/architecture/data-shapes/literal-replacement) describes.
- Check: With a `Server-Timing` header on page responses, a warm page shows a `dur` under 1 ms. After warming every page, the server's memory stays within the budget the load test sets.

Example:

```js
import { brotliCompressSync, gzipSync } from 'node:zlib';
import { createHash } from 'node:crypto';
const ENCODERS = { br: brotliCompressSync, gzip: gzipSync };
const cache = new Map();
function encoded(page, encoding) {
  const entry = cache.get(page.path) ?? {};
  entry[encoding] ??= ENCODERS[encoding](Buffer.from(render(page)));
  cache.set(page.path, entry);
  return entry[encoding];
}
// A function replacer inserts the content exactly as written
const fill = (template, html) => template.replace('<!--page-->', () => html);
```

```python
import gzip
ENCODERS = {"gzip": lambda body: gzip.compress(body, 9)}  # add "br" with the brotli package
cache = {}
def encoded(page, encoding):
    entry = cache.setdefault(page.path, {})
    if encoding not in entry:
        entry[encoding] = ENCODERS[encoding](render(page).encode())
    return entry[encoding]
# str.replace inserts the content exactly as written
fill = lambda template, html: template.replace("<!--page-->", html)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers website playbook: https://agentwranglers.se/playbook/website/site-architecture/render-at-build-time-serve-from-memory`.

- Previous: [Write each page once for every format](https://agentwranglers.se/playbook/website/site-architecture/one-source-many-formats)
- Next: [Build on HTML that already works](https://agentwranglers.se/playbook/website/site-architecture/progressive-enhancement)
- [Site architecture](https://agentwranglers.se/playbook/website/site-architecture)
