Measured in your browserWe advise on speed. We practice it.Loaded just now · real numbers from this visit, not a lab score.
Page loaded
First byte
DOM ready
First paint
Largest paint
DNS lookup
TLS handshake
Transferred
Saved by compression
Requests

API caching is where correctness anxiety kills the most free performance: teams that cheerfully cache images for a year refuse sixty seconds on a read-heavy endpoint. The techniques are mature, and GraphQL, the famously cache-hostile case, has workable answers too.

REST, done deliberately

Read endpoints get the full toolkit from this series: s-maxage per endpoint class, stale-while-revalidate for the refresh path, stale-if-error for resilience, ETags for cheap revalidation, and surrogate-key tags (entity:id) emitted per response so writes purge precisely. The design discipline is response-shape stability: personal data separated from shared data at the endpoint level, so the shared majority caches publicly and the personal minority stays private, rather than one mixed response poisoning both.

Why GraphQL resists

One endpoint, POST bodies, infinitely variable queries: the URL-keyed cache model finds nothing to hold. Each client’s query shape is a distinct response, intermediaries see opaque POSTs, and a single response mixes entities with wildly different freshness. The resistance is architectural, not incidental, which is why the fixes are architectural too.

The invalidation wiring is where API caching earns trust, so make it mechanical: every mutation knows which entities it touched; every cached response was tagged with the entities it contained; the mutation path emits purges for its tags on commit. That triangle, touch, tag, purge, gives you event-driven freshness with TTLs demoted to backstop, and it converts the correctness conversation from how stale can we tolerate into staleness bounded by purge latency, a number your CDN publishes and you verified in our purge article. Teams that wire the triangle cache aggressively and sleep; teams that rely on TTL guesswork cache timidly and still get burned.

Closing the GraphQL gap

The working ladder: persisted queries turn arbitrary query bodies into registered IDs fetched via GET, restoring URL-keyed edge caching for your actual query population (and shrinking requests as a bonus). Automatic persisted queries get most of that without a registry. Response-level TTLs computed from the minimum cache hint across the query’s fields let mixed responses cache honestly. And entity-level caching, at the edge in normalized-cache-aware layers or at origin in the data layer, rebuilds responses from cached entities, moving the cache below the query shape where reuse actually lives.

In practice

Start where the yield is: identify your top ten read queries by volume (they dominate; they always dominate), persist them, assign field-level cache hints, and put them behind edge caching with tag-based invalidation wired to your mutation path. Measure origin resolver load before and after. Most GraphQL estates discover that caching the head of their query distribution at the edge removes the majority of read load without touching the long tail’s flexibility, which was the trade GraphQL wanted you to think you couldn’t have.

API-caching engagements here deliver the tag ontology, the persisted-query rollout plan, and before/after origin load. The graph is the argument.

Get the free assessmentMore analysis