hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Caching with @hectoday/http

Why Caching

  • The Same Query, A Thousand Times
  • Project Setup

HTTP Caching

  • Cache-Control Headers
  • ETags and Conditional Requests
  • Stale-While-Revalidate

Server-Side Caching

  • In-Memory Caching with Map
  • TTL and Expiration
  • Cache-Aside Pattern
  • LRU Eviction

What to Cache

  • Caching Database Queries
  • Caching Computed Results
  • Caching External API Responses

Invalidation

  • The Hardest Problem
  • Time-Based Invalidation
  • Event-Based Invalidation
  • Tag-Based Invalidation

Putting It All Together

  • Caching Checklist
  • Capstone: Caching the Book Catalog

Stale-While-Revalidate

The revalidation delay

With max-age=60, the browser serves instantly from cache for 60 seconds. At second 61, it must contact the server to revalidate — even if the data has not changed. The user sees a brief delay while the browser waits for the 304 response.

stale-while-revalidate eliminates this delay.

How it works

Cache-Control: public, max-age=60, stale-while-revalidate=300

This means:

  • For the first 60 seconds: serve from cache (fresh).
  • From 60 to 360 seconds: serve the stale cached version immediately AND revalidate in the background. The user gets an instant response. The cache is updated for the next request.
  • After 360 seconds: the cache is too stale. Wait for a fresh response from the server.

The user never waits for revalidation. They always get an instant response. The worst case is a response that is up to 5 minutes old — which for a book catalog is acceptable.

The timeline

0s          60s                     360s
|── fresh ──|── stale-while-reval ──|── must revalidate ──|
|           |                       |                      |
| Instant   | Instant (stale) +     | Wait for server     |
| from      | background refresh    |                      |
| cache     |                       |                      |

Adding it to responses

route.get("/books/top", {
  resolve: () => {
    const books = db.prepare("SELECT ...").all();
    return new Response(JSON.stringify(books), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=60, stale-while-revalidate=300",
      },
    });
  },
});

Updating the helper

const PROFILES: Record<string, string> = {
  "public-long": "public, max-age=3600, stale-while-revalidate=86400",
  "public-short": "public, max-age=60, stale-while-revalidate=300",
  private: "private, max-age=60",
  "no-cache": "no-cache",
  "no-store": "no-store",
};

[!NOTE] stale-while-revalidate is part of RFC 5861 and is supported by modern browsers and CDNs. It does not apply to private responses because CDNs (which handle background revalidation) cannot cache private data. For private data, use ETags instead.

When to use stale-while-revalidate

Use it for: public data that changes occasionally but where instant responses matter more than perfect freshness. Book listings, product catalogs, blog posts, documentation.

Do not use it for: private data (private caches do not support background revalidation), rapidly changing data (stock prices), or data where staleness has real consequences (bank balances, inventory counts for purchase decisions).

Combining with ETags

For maximum efficiency, combine all three:

Cache-Control: public, max-age=60, stale-while-revalidate=300
ETag: "abc123"
  1. 0-60s: Browser serves from cache. No request.
  2. 60-360s: Browser serves stale cache immediately and revalidates in the background using the ETag.
  3. Background revalidation: Server checks ETag. Unchanged → 304 (no body). Changed → 200 with new data.
  4. After 360s: Browser waits for fresh response.

The user always gets an instant response. The server only sends data when it has actually changed. This is the most efficient caching strategy for public read-heavy APIs.

Exercises

Exercise 1: Set max-age=5, stale-while-revalidate=30. Wait 6 seconds (past max-age but within stale window). Make a request. Verify it returns immediately (stale) and a background revalidation happens.

Exercise 2: Wait 35 seconds (past the stale window). Make a request. Verify the browser waits for the server.

Exercise 3: Combine stale-while-revalidate with ETags. Verify background revalidation uses the ETag and gets 304 when data is unchanged.

What does the browser do during the stale-while-revalidate window?

← ETags and Conditional Requests In-Memory Caching with Map →

© 2026 hectoday. All rights reserved.