Skip to content

Edge internals

Chapter 14. Chapter 8 introduced the edge from outside. This is its inside — tenant resolution, the read-survival cache flow, and the circuit breaker, in code.

The line-level mechanics of the edge. Two layers: packages/edge-core is the portable, platform-agnostic library (nothing Cloudflare-specific — adapters inject KV, cache, fetch, dataset); apps/edge/src/worker.ts is the Cloudflare adapter that wires Workers KV, caches.default, neon(), and Analytics Engine into it.

Request orchestration — worker.ts

One Hono app. app.onError(() => storeUnavailable()) turns any unhandled throw into a branded 503 (never a raw 500). A use('*') middleware wraps every request:

  • reqIdsanitizeReqId(header('x-request-id')) ?? crypto.randomUUID() (adopt a validated client id, else mint).
  • finally-log — logs in a finally (so a downstream throw is still recorded): { tenant, status, stale, ms, path } where status = threw ? 503 : res.status and path is pathname only. Then one Analytics Engine metric point.

The main all('*') route: block /__* (404) → resolve tenant → 404 if none → 503 if no ORIGIN_URLfetchViaOriginpublicHeaders. The edge never renders — no origin ⇒ 503.

Tenant resolve — tenant-resolve.ts

lookupTenant(host, kv, dbQuery) is KV-first, and "not found" is two different states:

KV readMeaningAction
JSON {"t":"t_x"}founduse the id, no DB
JSON {"t":null}known-badreturn null, no DB (neg cache)
kv.get() returns nullabsentDB lookup once, then cache back
  • DB lookup is wrapped in withTimeout(dbQuery(host), 800ms); the verified-claim SQL is SELECT tenant_id FROM domains WHERE host=$1 AND verified=trueverified=true is load-bearing (an unverified squat must not serve, and stays reclaimable).
  • On success it writes KV: positive TTL 3600s / negative TTL 60s.
  • A timed-out lookup is deliberately NOT cached — caching a negative on a transient DB blip would 404 a real store for 60s. (Caveat: the pending Neon query can't be cancelled; it leaks until GC — correctness rests only on not persisting its result.)

The adapter also honors ?store=<id> before KV/DB — but only when storeOverrideAllowed(host) (localhost-gated).

Serve-origin — the read-survival flow — serve-origin.ts

  • Reads get a 1.5s budget; writes get 10s and never serve stale (canServeStale = isRead && !!cache). A dead-origin write becomes a 503, not a faked page — a durable mutation can't come from cache.
  • Freshness, not mere presence, short-circuits the originisFresh checks (now - x-ratio-cached-at) < ttl. A stale copy does not short-circuit; it's only served if a revalidation then 5xxs/times-out.
  • Cacheability (isCacheable): a set-cookie response is never cached (per-user); no-store/private → not cached; TTL comes from s-maxage (preferred) else max-age, must be > 0. A page that forgets s-maxage is silently never cached.
  • On failure the breaker is fed onFailure(); on success onSuccess().

Circuit breaker — circuit-breaker.ts

createCircuitBreaker(5, 10_000) — a closure over failures + openedAt (no explicit half-open flag):

It's module-scoped → per-isolate: each isolate learns independently, no fleet consensus, a cold isolate starts closed. Net effect: a sustained outage costs an isolate ~5 timeouts total, not one per request.

Headers & the edge secret — headers.ts, edge-secret.ts

  • proxyInit injects fresh trusted headers x-edge-auth + x-ratio-tenant (never forwards a client copy), forwards only an allowlist (content-type, accept, accept-language, cookie), and sets redirect: 'manual' — a cart write answers 303 → /cart with a Set-Cookie; if the edge followed it, the cookie would be swallowed and the followed GET would be cookieless. 'manual' returns the 3xx with the cookie so the browser follows it.
  • publicHeaders strips internal headers by prefix allowlist — only x-content-type-options + x-request-id survive; every other x-* is dropped. (This is an allowlist because the old denylist drifted and leaked x-theme-render/x-theme-version.)
  • storeOverrideAllowed gates ?store= to localhost/*.localhost only — it must never loosen to *.workers.dev (live in prod) or a visitor could render an arbitrary tenant and poison the 1-year CDN cache.
  • resolveEdgeSecret returns EDGE_SECRET, else the dev literal only when RATIO_LOCAL === 'true', else throws (fail closed). The gate is RATIO_LOCAL, not NODE_ENV (the old NODE_ENV check wrongly handed the literal to staging).
  • The constant-time compare lives at the origin (edgeAuthOktimingSafeEqual), which gates every request. The edge sends the secret; the origin verifies it and takes the tenant from the header only.

Logs, metric, 503

  • Access log — one line per request, a fixed field allowlist (tenant, method, path, status, stale, ms), path is pathname only (never the query string, which can carry tokens).
  • Metric — one Analytics Engine point with exactly one index = tenant (bounded cardinality; AE samples the long tail).
  • storeUnavailable() — a self-contained 503 (inline CSS, retry-after: 30, no-store, the storefront CSP), generated entirely at the edge so it always renders.

Portability (Akamai)

There's no EdgeProvider interface — the seam is plain dependency injection: lookupTenant(kv, dbQuery), fetchViaOrigin(cache, doFetch, breaker), createCircuitBreaker(…, now) all take their platform bindings as arguments and never reach for globals. TenantKV/EdgeCache/AnalyticsEngineDataset are minimal local interfaces. An Akamai adapter would inject EdgeKV, DataStream 2, and back the breaker with EdgeKV (per-isolate state is unreliable there). No Akamai adapter exists in-tree yet.

Gotchas

  1. ?store= bypasses KV/DB — safe only because it's localhost-gated; loosening it to a public host would allow arbitrary-tenant rendering + 1-year cache poisoning.
  2. Query string is in the cache key but not in logs?utm_* fragments the CDN cache per distinct query, and you can't see it in the pathname-only access log.
  3. Negative cache 404s a store for up to 60s unless a control-plane write-through updates the KV key.
  4. A forgotten s-maxage = silently never cached (no error) → every view hits the origin.
  5. Stale is served only on an actual origin failure, never proactively; a stale-but-present copy still revalidates.
  6. Writes never serve stale and use a 10s budget — a dead-origin write is a 503, not a page.
  7. Breaker is per-isolate, not global; cold isolates start closed.
  8. Any new client-facing x-* header must be added to the publicHeaders allowlist or it's stripped.
  9. The edge sends EDGE_SECRET ?? '' — fail-closed lives at the origin verifier, not the edge.