Skip to content

Public sites perf · Phase 3: optional, vendor-neutral edge cache / CDN support #73

Closed

Description

@clbotdev

Part of the public-site performance plan (master issue linked above). Depends on Phase 2.

Goal

Let any HTTP cache or CDN sit in front of apps/sites using standard headers only. It stays optional and vendor-neutral, so it works for self-hosters. Correct delivery never depends on it, because Layer 1 (Phase 2) does the real caching.

Modes (EDGE_CACHE_MODE)

Mode Headers Purge Works with
none (default) normal Next behaviour none anything, including no CDN
ttl s-maxage=$EDGE_CACHE_TTL (default 60), stale-while-revalidate=1d, stale-if-error=7d none; staleness capped at the TTL any CDN or caching proxy, no integration
purge long s-maxage, plus Surrogate-Key and Cache-Tag outbox calls a purge adapter anything an adapter exists for

Tasks

  • Emit headers per mode. Compute tags in apps/sites/proxy.ts from the URL path (t:<team>, t:<team>:docs, t:<team>:page:<path>, ...) and confirm they survive the rewrite. Browser Cache-Control stays max-age=0, must-revalidate.
  • Never edge-cache /help/tickets/*, chat, search, form APIs, previews, or the webhook routes.
  • Purge adapter interface purge(tags[]), with a generic http adapter: a signed POST of { tags } to EDGE_PURGE_URL with EDGE_PURGE_SECRET.
  • Outbox destination for edge purges, with retries and acknowledgments. Order: Layer 1 revalidateTag → edge purge → a second edge purge about 10 seconds later (catches renders that were already in progress).
  • Sensitive-data purge events count as complete only after the edge purge is acknowledged.
  • RSC / _rsc check: the cache key must include the query string so HTML and RSC payloads are never mixed up. Test client-side navigation behind a CDN that ignores Vary.
  • Optional ASSET_PREFIX for serving _next/static from a separate host.
  • Optional self-hosted recipe: Caddy (TLS, unchanged) → Varnish with xkey → apps/sites, wired through the http adapter. Documented and opt-in, not part of the default stack.
  • Docs: configuration reference for the EDGE_* variables.

Done when

  • ttl mode works behind an unmodified CDN or proxy.
  • purge mode with the Varnish recipe shows a publish within a few seconds.
  • Client-side navigation stays correct.

Activity

  1. clbotdev commented on Oct 25, 2026

    @clbotdev
    ContributorAuthor

    Implemented in #144 (stacked on #143). The done-when checks:

    • ttl mode behind an unmodified proxy: with EDGE_CACHE_TTL=5 and no purge integration, publishes appear after 5.0–5.1 s.
    • purge mode with the Varnish recipe: a publish is visible at the edge in 0.20–0.54 s. Purges are scoped by tag, retried if the receiver is down, and repeated after 10 s.
    • Client navigation stays correct behind Varnish, both with Vary honored and with Vary stripped. Testing with Vary stripped found an RSC cache-poisoning case: a single rsc-header request could get a redirect cached at the page URL. It's fixed and retested.

    Through Varnish: 4.9k–7.4k req/s at p50 1 ms, vs 140–242 req/s at the origin.

  2. added a commit that references this issue on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions