Skip to content

PATTERN Cited by 1 source

Stale-while-revalidate from IndexedDB

Shape

A client-side cache pattern that pairs the stale-while-revalidate semantic with IndexedDB as the persistent storage substrate, optionally fronted by a synchronous in-memory cache tier. On every navigation, the client renders immediately from the local cache if a value exists, then revalidates against the origin in the background and reconciles the in-memory store on diff.

In its full three-tier form (the GitHub issues#show shape):

read path (hottest first):
  React in-memory store      ← already-rendered payload
   ↓ on miss
  in-memory cache            ← synchronous; hot payloads served without async boundary
   ↓ on miss
  IndexedDB persistent cache ← survives tab close + restart; async read
   ↓ on miss
  network                    ← background revalidation, populates all three tiers

When to use it

  • The application is a long-running web app where users revisit the same resources repeatedly (issue triage, collaboration loops, browsing across tabs).
  • The data is bounded-stale-tolerant — a few seconds of cached-vs-server divergence on read is acceptable product-wise.
  • You want to render-from-cache instantly on soft navigation, not pay a network round-trip per click.
  • You want the cache to survive browser restart and tab close.

If any of these is false (write-heavy / strict-consistency- required / single-session / disposable), use a simpler tier (plain in-memory only, or origin-only).

Read path (canonical sequence)

  1. User initiates a soft navigation.
  2. Look up the resource in the active in-memory store.
  3. If absent, look up in the in-memory cache (synchronous).
  4. If absent, look up in IndexedDB (async).
  5. If found at any tier, render immediately from that value.
  6. In parallel, issue a background fetch to the origin.
  7. On origin response: if the new value differs from cached, reconcile the in-memory store and re-render; write through to the in-memory cache and IndexedDB.

Write path

  • Every successful network fetch writes through to all three tiers (in-memory store, in-memory cache, IndexedDB).
  • Cache eviction policy in the in-memory tier is bounded by available memory; in IndexedDB it is bounded by quota + application-controlled TTL.

Failure handling

The pattern's graceful-degradation property is load- bearing: "when network is degraded, users still get a usable page from cache, with freshness reconciled once connectivity recovers, introducing a new graceful-degradation model." (Source: sources/2026-05-14-github-from-latency-to-instant-modernizing-github-issues-navigation-performance)

If the origin is unreachable, the cached value is served unchanged; revalidation is retried (or queued) until connectivity returns.

The cache-hit-ratio viability threshold

Before committing to the architecture, GitHub set an explicit cache-hit-ratio floor for the design to be worthwhile: "Our pre-workstream analysis showed a strong repeated-access pattern: users reopen the same issues frequently during triage and collaboration loops. Based on that behavior, we estimated a potential cache-hit ratio of roughly 30 % for issues#show and used that as the initial viability threshold." The post-rollout observed ratio of ~33 % cleared the threshold and validated the architecture.

This is a defensible discipline: a SWR-from-IndexedDB cache is only worth building if the observed cache-hit ratio will exceed a numerical floor that justifies the engineering cost plus the staleness cost. Build it only if your access-pattern data predicts hitting the floor.

Quantified outcomes (canonical instance)

GitHub's issues#show rollout, Step 1 (cache layer alone, no preheating yet):

  • Pre-launch React-soft-nav instant share: 4 %
  • Post-launch instant share: 22 %
  • Total request volume affected: ~15 %
  • Cache-hit ratio: ~33 %
  • Server/cache divergence rate: 4.7 %

After preheating and the in-memory tier were added on top, instant share went to ~70 % and cache-hit ratio to ~96 %.

Tradeoffs (named)

  • Controlled staleness vs latency. "This is not 'cache or correctness.' It is latency-first rendering with asynchronous consistency checks on the same navigation." The 4.7 % cache/server divergence at GitHub is treated as an explicit operating envelope.
  • IndexedDB asynchronous reads. Even the persistent layer has an async boundary on the critical render path. The in-memory tier in front of IndexedDB is what eliminates this for hot payloads — see patterns/pair-fast-small-cache-with-slow-large-storage.
  • Quota and eviction. IndexedDB quota is browser-scoped and not infinite; high-write workloads can hit quota limits.
  • Cache-hit-ratio floor must hold in production. If the predicted ~30 % floor doesn't materialise, the engineering cost has been spent for marginal gain.

Relationship to other patterns

Seen in

Last updated · 542 distilled / 1,571 read