Accepted
ADR 0055 — Site-wide chrome drains every prerender
A navigation, menu, or site-title change drains the whole prerender prefix, not just the home and posts listing
Cusp pre-renders each public page to HTML at publish time and stores it in R2 at rendered/{path}/index.html. The render bakes the whole page, including the site chrome — the primary navigation and section menus in the header and sidebar, the site title and description in the document head, and the locale on the html element. That chrome is identical across pages but it is stored independently in every page's prerendered blob.
Context
When a setting that feeds the chrome changed — site.primary_nav, site.menus, site.title, site.description, site.locale, or site.homepage_page_slug — the admin settings handler purged the cache layer (the settings and nav surrogate keys) and deleted just two R2 objects: the home page and the posts listing. Every other prerendered page kept its old chrome baked into its R2 blob, and the public render path's R2 fast path kept serving it. A page only picked up the new chrome when it was next individually republished.
The visible failure: on the documentation site, which renders a per-section sidebar menu, adding a page to a section menu updated the menu setting but did not re-render the section's existing pages. Two pages in the same section then showed different sidebars — the newly published page had the new menu, its older siblings still showed the old one. It read as a cache bug because it was one: the cache layer was purged but the R2 origin underneath it was not.
The asymmetry is the tell. A theme-token change — which alters the inline style block baked into every page exactly the way the nav is baked in — already drained the entire rendered/ prefix and bumped the cache epoch. Navigation and the rest of the site chrome are the same class of site-wide input, but they were treated as a bounded, two-path purge on the false premise that nav only appears on listing pages.
Decision
Every renderer-relevant setting is site-wide chrome, so a write to any of them drains the whole prerender prefix and bumps the cache epoch — the same site-wide invalidation the theme-token path uses. The settings handler calls purgeAllPrerenders (which paginates and deletes the entire rendered/ prefix from R2) and bumpCacheEpoch, rather than deleting a hand-picked list of two paths. Resetting one of these settings to its default through the delete route does the same. After the drain, the next request to any page misses both the cache and R2, renders live from current state with the new chrome, and repopulates R2.
The renderer-relevant set is the canonical trigger list: site.primary_nav, site.menus, site.title, site.description, site.locale, and site.homepage_page_slug. A change to any other setting does not touch the public prerenders.
Alternatives considered
- Track which pages embed which menu and drain only those. Rejected: the primary nav, title, and description appear on every page regardless of section, so the precise set is almost always the whole site. The bookkeeping buys nothing and adds a dependency map to keep correct.
- Resolve the chrome at request time instead of baking it into the prerender. Rejected for v1: it contradicts the hybrid-render invariant that a prerendered page is served whole from R2, and it would push a nav resolution and a menu read onto every cache miss. A request-time chrome overlay is a possible future optimisation, not a fix for a correctness bug.
- Leave the bounded purge and rely on republishing. Rejected: it makes correct chrome depend on an editor remembering to republish every page after every nav edit, which is exactly the kind of hidden manual step the project treats as a defect.
Consequences
- A nav, menu, title, description, or locale change is coherent across the whole site on the next request to each page — no page can be left showing stale chrome.
- These changes now cost a full rendered/ drain and a wave of live re-renders as pages are next requested. They are infrequent administrative actions, and the cost is the same one theme-token changes already pay, so it is accepted.
- The seed and re-seed path inherits the fix for free: it writes the menu settings through the same handler, so a re-seed that changes a menu drains the prerenders without a manual cache lever.
- On a default workers.dev deploy the epoch bump is what carries the invalidation across colos; on a custom domain the drain plus the zone cache behaviour both apply.
For the prerender layers, surrogate-key purges, and the cross-colo cache epoch this decision builds on, see Rendering and cache, and the cross-colo cache-epoch mechanism in ADR 0012.