Repository navigation
Public sites perf · Phase 2: built-in caching layer (cache components + shared Redis handler, docs cacheable) #72
Description
Part of the public-site performance plan (master issue linked above). Can run alongside Phase 1. Design: docs/site-delivery.md §4.
Goal
Stop building public pages on demand from the API and database. FrontLit caches rendered pages and data itself and invalidates them by tag on publish. This must work with a plain docker compose up and no CDN.
| Deployment | Cache storage | Extra infra |
|---|---|---|
| Single container | Next's default in-memory/disk cache | none |
| Multiple replicas | Shared Redis cache handler with refreshTags() for invalidation across replicas |
Redis, already in docker-compose.yml (REDIS_URL, optional) |
Decision recorded
Docs are no longer exempt from caching. docs-management.md §23.3's "zero staleness / no-store" rule is replaced by: docs HTML is cached like other public pages and invalidated through the signed outbox webhook when a publication goes live or a policy changes. Staleness is bounded by webhook delivery, normally under a second. Search, chat answers and the public docs API keep authoritative, uncached reads. If the origin is down, the last cached publication may be served.
Tasks
- Remove request-header dependencies so routes can be cached:
x-frontlit-embedinapps/sites/app/sites/[teamId]/layout.tsx: move/embedinto its own route group and layout.x-frontlit-canonical-hostinapps/sites/lib/request-host.ts: get the canonical host from cached team data (add it to public settings).
- Turn on cache components (
cacheComponents,"use cache: remote",cacheTag,cacheLife) and replace the fixed 60-secondteamCacheinapps/sites/lib/api.ts. - Tag shell and content separately:
shell:<team>: settings, theme, branding, nav/footer, chatbot settings, code injection.docs:<team>,page:<team>:<path>,blog:<team>,changelog:<team>(sitemap and RSS follow their collection).- Shell changes clear
shell:<team>; content publishes clear only their content tag.
- Docs:
- Cache
getDocumentationSnapshotunderdocs:<team>instead ofno-store. - Remove the
/docsno-storeoverride inapps/sites/proxy.ts. - Have
app/api/webhooks/documentation/route.tscleardocs:<team>and the sitemap instead of the whole team.
- Cache
- Move all site invalidation onto the outbox.
revalidateTeaminapps/api/src/services/revalidate.tsis a best-effort POST today, which is acceptable only because of the 60-second cache. Change the/api/webhooks/revalidatepayload to{ eventId, teamId, tags[], invalidateHosts? }, accept only known tag families for that team, and add replay protection. Event-to-tag mapping:docs/site-delivery.md§4.5. - Shared header/footer: page reads also carry
shell:<team>, because the API hydrates the published header/footer into every page. A publish that promotes a changed header/footer draft invalidatesshell:<team>. - Cross-replica invalidation: add a Redis cache handler (
cacheHandlers.remote) implementingrefreshTags(). Keep the in-memory default whenREDIS_URLis unset. - Host resolution cache: move the per-pod
hostCacheinapps/sites/lib/host.tsto Redis when available, so domain-change invalidation reaches every replica. - Multi-instance basics from the Next self-hosting guide: consistent build ID /
deploymentId, and a shared server-actions encryption key if applicable. - Leave uncached:
/help/tickets/*, chat, search, forms and previews stay dynamic. - Update docs:
docs/site-delivery.md(new),docs-management.md§23.3,architecture.md§12.2 and §13.5.
Done when
- Repeat views of pages, blog and docs don't call the API.
- Publishing updates every replica within about a second.
- A single-container setup with no Redis still works.
Activity
clbotdev commentedon Oct 25, 2026
Implemented in #143 (all tasks). Measured on the sample tenant, against
main:Changes from the plan:
pages:<team>tag was dropped: nothing reads page lists except the sitemap, which has its own tag.DEPLOYMENT_IDcovers the build ID.Merge pull request #143 from codelitdev/perf/sites-layer1-cache