| |

REST API 17 ๐ŸŒ Caching Strategies (Cache-Control, ETag, Last-Modified)

โฑ๏ธ Estimated read: 14 min | |

HTTP caching is not a single header but a layered contract between the server, the client, and any intermediary caches. Cache-Control defines how long a response remains fresh and who may store it. ETag and Last-Modified define how to verify whether a stored response is still valid after freshness expires. The server that sends only Cache-Control without validators forces clients to re-download the full response after the TTL lapses. The server that sends only validators without Cache-Control forces clients to revalidate on every request, even when the data has not changed for hours. The combination is what produces the bandwidth savings and latency reduction that caching promises.

The three mechanisms are not interchangeable. Cache-Control is a directive: it tells caches what they are allowed to do. ETag is an opaque validator: it is a fingerprint of the response body that the client returns in If-None-Match to ask “has this changed?” Last-Modified is a timestamp validator: the client returns it in If-Modified-Since and the server compares it against the resource’s modification time. RFC 9111 replaced RFC 7234 as the definitive caching specification, and it preserves the precedence rule: when both validators are present, ETag takes priority over Last-Modified .

This chapter covers the Cache-Control directives that matter for APIs, the strong-versus-weak ETag distinction and how to generate each, the Last-Modified fallback and its granularity limitation, the conditional request flow that produces 304 Not Modified, and the invalidation strategies that prevent stale data from persisting. It assumes familiarity with HTTP status codes and the request/response cycle.

Key point: Cache-Control controls freshness; ETag and Last-Modified control validation. A response without validators cannot be revalidatedโ€”only re-downloaded. A response with validators but without Cache-Control is revalidated on every request, which saves bandwidth but not latency. The correct pattern sends both, with ETag as the primary validator and Last-Modified as the HTTP/1.0 fallback.


Why caching requires explicit headers

Heuristic caching is unpredictable. When a response lacks Cache-Control, browsers and intermediaries may apply heuristic freshness based on Last-Modified or other signals. The result varies by cache implementation, browser version, and response headers. An API endpoint that returns user data without Cache-Control: private may be cached by a shared proxy and served to another user. The fix is to always set Cache-Control explicitly, never relying on the cache to guess .

Validators enable bandwidth savings without sacrificing freshness. An ETag or Last-Modified header allows the client to send a conditional request. If the resource has not changed, the server returns 304 Not Modified with no body. The client reuses its stored copy. This is the mechanism behind GitHub’s API, where conditional requests that return 304 do not count against the rate limit . The bandwidth saved is the entire response body; the latency saved is the serialization and transmission time.

The private directive prevents cross-user leaks. A response marked public may be stored by any cache, including shared proxies and CDNs. A response marked private may be stored only by the client’s browser. For authenticated endpoints that return user-specific data, private is mandatory. Without it, a misconfigured CDN can serve one user’s account data to another .

The trade-off. Every cached response is a potential stale response. max-age is a promise that the data will not change for that many seconds. If the data changes before the TTL expires, the client sees stale data. The mitigations are shorter TTLs, event-based invalidation, and directives like stale-while-revalidate that serve stale data while fetching a fresh copy in the background. The choice depends on the business cost of staleness versus the performance cost of revalidation.


a. Cache-Control: directives, freshness, and scope

The Cache-Control header contains one or more directives that control storage and reuse. The directives that matter for APIs are public, private, no-cache, no-store, max-age, s-maxage, must-revalidate, stale-while-revalidate, and immutable .

public allows any cache to store the response. private restricts storage to the client’s browser. no-cache allows storage but requires revalidation before every reuseโ€”the cache must check with the origin before serving the stored copy. no-store prohibits storage entirely, for responses that must never persist (financial data, PII) . The distinction between no-cache and no-store is a common point of confusion: no-cache means “store but check first”; no-store means “do not store.”

max-age=N sets the freshness lifetime in seconds. A response with max-age=60 is fresh for 60 seconds; after that, the cache must revalidate or re-fetch. s-maxage=N overrides max-age for shared caches (CDNs, proxies), allowing a longer TTL at the edge than in the browser. stale-while-revalidate=N allows a stale response to be served for N seconds beyond max-age while the cache fetches a fresh copy in the background. immutable tells the cache that the response body will never change for this URL, so revalidation is unnecessaryโ€”useful for versioned assets .

A sensible default for an authenticated read endpoint is private, max-age=60, stale-while-revalidate=120 . This keeps user data out of shared caches, allows the browser to serve a cached copy for one minute, and serves a slightly stale copy for up to two more minutes while refreshing in the background. For public reference data, public, max-age=300, s-maxage=86400, stale-while-revalidate=86400 gives the CDN a 24-hour TTL and the browser a 5-minute TTL .


b. ETag: strong versus weak, and how to generate them

An ETag is an opaque string that identifies a specific version of a response body. The server sends it in the ETag header. The client stores it and sends it back in If-None-Match on the next request for the same URL. The server compares the client’s ETag against the current resource’s ETag. If they match, the server returns 304 Not Modified with no body. If they differ, the server returns 200 OK with the new body and a new ETag .

ETags come in two flavors. A strong ETag (ETag: "33a64df551425fcc") promises byte-for-byte equality. Two responses with the same strong ETag are identical. A weak ETag (ETag: W/"33a64df551425fcc") promises semantic equivalence: the bytes may differ (field ordering, whitespace, a timestamp), but the meaning is the same. Strong ETags are required for byte-range requests and for optimistic concurrency control with If-Match. Weak ETags are acceptable when the representation can vary without changing the underlying data .

The generation strategy matters for performance. A body hash (MD5 or SHA-1 of the serialized response) is accurate by construction but requires building the full response on every request, including the requests that return 304. A version column or updated_at timestamp derived from the database is cheaper: a conditional request costs one indexed lookup instead of full serialization. The version must bump on every change that affects the response, including changes in joined tables. Missing a bump produces stale 304s, which are invisible bugs . The recommendation is to start with body hashing for correctness and move hot endpoints to version-based ETags when profiling shows serialization cost matters.

Compression middleware can rewrite strong ETags to weak ones when it gzips a response on the fly, because the compressed bytes no longer match the original. If concurrency checks fail behind a proxy, look for a W/ prefix that was not present when the application server sent the response .


c. Last-Modified: the fallback validator

Last-Modified is a timestamp indicating when the origin server believes the resource was last modified. The client sends it back in If-Modified-Since. The server compares the timestamp against the resource’s current modification time. If the resource has not been modified since the client’s timestamp, the server returns 304 Not Modified .

Last-Modified has two limitations that make ETag the preferred validator for APIs. First, its granularity is one second. A resource that changes twice within the same second may have the same Last-Modified value for both versions, causing a 304 to be returned for a request that should receive the new version. Second, it is susceptible to clock skew between the server that generated the timestamp and the cache that stores it .

Despite these limitations, Last-Modified serves an important compatibility purpose. HTTP/1.0 clients and caches ignore ETags but understand Last-Modified. The W3C recommendation is for origin servers to send both a strong ETag and a Last-Modified value. HTTP/1.1 clients use the ETag in If-None-Match and fall back to Last-Modified in If-Modified-Since when no ETag is present . When both validators are present, If-None-Match takes precedence per RFC 9110 .

The conditional request flow is the same for both validators. The client sends the validator from its stored response. The server evaluates the condition. A match produces 304 Not Modified with the same Cache-Control, ETag, and Last-Modified headers as the original response would have had, but no body . A mismatch produces 200 OK with the new body and new validators.


Complete Example Session

# ============================================
# PART 1: INITIAL RESPONSE WITH FULL HEADERS
# ============================================
GET /v1/products/42 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400
ETag: "9f8b2c41aa73e0d5"
Last-Modified: Wed, 15 Oct 2025 14:30:00 GMT

{
  "id": 42,
  "name": "Widget",
  "price": 29.99
}

# ============================================
# PART 2: CONDITIONAL REQUEST (ETAG)
# ============================================
GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "9f8b2c41aa73e0d5"

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=300, s-maxage=3600
ETag: "9f8b2c41aa73e0d5"
Last-Modified: Wed, 15 Oct 2025 14:30:00 GMT

# No body. Client uses its stored copy.

# ============================================
# PART 3: CONDITIONAL REQUEST (LAST-MODIFIED FALLBACK)
# ============================================
GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-Modified-Since: Wed, 15 Oct 2025 14:30:00 GMT

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=300
Last-Modified: Wed, 15 Oct 2025 14:30:00 GMT

# ============================================
# PART 4: RESOURCE CHANGED โ€” NEW ETAG
# ============================================
GET /v1/products/42 HTTP/1.1
Host: api.example.com
If-None-Match: "9f8b2c41aa73e0d5"

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300
ETag: "a1b2c3d4e5f6a7b8"
Last-Modified: Wed, 15 Oct 2025 15:00:00 GMT

{
  "id": 42,
  "name": "Widget",
  "price": 24.99
}

# ============================================
# PART 5: PRIVATE USER DATA
# ============================================
GET /v1/me HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Cache-Control: private, max-age=60, stale-while-revalidate=120
ETag: "user-42-v17"

# private: browser only, no CDN.
# max-age=60: fresh for 1 minute.
# stale-while-revalidate: serve stale up to 2 min.

# ============================================
# PART 6: NO-STORE FOR SENSITIVE DATA
# ============================================
GET /v1/transactions HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
Cache-Control: no-store

# No caching at all. Financial data.

# ============================================
# PART 7: OPTIMISTIC CONCURRENCY WITH IF-MATCH
# ============================================
PUT /v1/products/42 HTTP/1.1
Host: api.example.com
If-Match: "9f8b2c41aa73e0d5"
Content-Type: application/json

{"price": 24.99}

HTTP/1.1 200 OK
ETag: "a1b2c3d4e5f6a7b8"

# If-Match mismatch โ†’ 412 Precondition Failed.
# Prevents lost updates when two clients edit concurrently.

# ============================================
# PART 8: INVALIDATION ON WRITE
# ============================================
POST /v1/products/42/reviews HTTP/1.1
Host: api.example.com

HTTP/1.1 201 Created
Cache-Control: no-store
# Response itself not cached; but the POST invalidates
# the cached GET /v1/products/42 on the client and CDN.

# ============================================
# PART 9: VERSIONED ASSET (IMMUTABLE)
# ============================================
GET /assets/app.abc123.js HTTP/1.1

HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "abc123"

# URL contains version. Content never changes.
# immutable skips revalidation entirely.

# ============================================
# PART 10: THE DECISION FLOW
# ============================================
# Is the data user-specific? โ†’ private
# Is it sensitive (PII, financial)? โ†’ no-store
# Is it public reference data? โ†’ public + s-maxage
# Is the URL versioned? โ†’ immutable
# Otherwise โ†’ max-age + stale-while-revalidate
# Always send ETag (strong by default).
# Send Last-Modified for HTTP/1.0 fallback.

Quick Reference

Cache-Control Directives

DirectiveEffectUse For
publicAny cache may storePublic data
privateBrowser onlyUser-specific data
no-cacheStore but revalidate every useFreshness-critical with 304s
no-storeNever storePII, financial data
max-age=NFresh for N secondsPrimary TTL
s-maxage=NCDN TTL overrideEdge caching
stale-while-revalidate=NServe stale N seconds while refreshingRead-heavy endpoints
immutableNever revalidateVersioned URLs

Validator Comparison

AspectETagLast-Modified
TypeOpaque stringTimestamp
GranularityExact1 second
PriorityPrimary (RFC 9110)Fallback
Header (request)If-None-MatchIf-Modified-Since
Concurrency controlIf-Match โ†’ 412Not applicable
HTTP/1.0 supportIgnoredSupported

ETag Types

TypePrefixGuaranteeUse For
StrongNoneByte-for-byte identicalRange requests, If-Match
WeakW/Semantic equivalenceVariant representations

Caching by Data Type

Data TypeCache-ControlRationale
Static referencepublic, max-age=300, s-maxage=86400Rarely changes
Product catalogpublic, max-age=300, s-maxage=3600High read volume
User-specificprivate, max-age=60No CDN
Financial/PIIno-storeNever cache
Versioned assetspublic, max-age=31536000, immutableURL versioned

Best Practices

โœ… Do This:

# Always set Cache-Control explicitly.                       // โœ…
Cache-Control: private, max-age=60, stale-while-revalidate=120

# Send both ETag and Last-Modified.                          // โœ…
ETag: "9f8b2c41aa73e0d5"
Last-Modified: Wed, 15 Oct 2025 14:30:00 GMT

# Use strong ETags by default.                               // โœ…
ETag: "9f8b2c41aa73e0d5"

# Return 304 with no body on match.                          // โœ…
HTTP/1.1 304 Not Modified

# Use private for user-specific data.                        // โœ…
Cache-Control: private, max-age=60

โŒ Don’t Do This:

# Rely on heuristic caching (no Cache-Control).              // โŒ
# Unpredictable; may leak user data via shared caches.

# Use public for authenticated endpoints.                    // โŒ
Cache-Control: public  # CDN may serve user A's data to user B.

# Use Last-Modified as the only validator.                   // โŒ
# 1-second granularity; misses rapid changes.

# Use weak ETags for If-Match concurrency control.           // โŒ
If-Match: W/"..."  # Weak ETags not allowed for If-Match.

Common Pitfalls

PitfallWhy It HappensFix
Cross-user data leakpublic on authenticated endpointUse private
Stale data after TTLNo revalidation or invalidationmust-revalidate or shorter TTL
304 for changed resourceETag not bumped on updateVersion column or body hash
Compression rewrites ETaggzip changes bytesAccept weak ETag or disable compression
Lost updatesNo If-Match on writesAdd If-Match with ETag
CDN serves stale after origin downstale-if-error not configuredSet explicitly or stale-if-error=0

Real-World Examples

1. Public Reference Data

Cache-Control: public, max-age=300, s-maxage=86400, stale-while-revalidate=86400

2. User-Specific Endpoint

Cache-Control: private, max-age=60, stale-while-revalidate=120

3. Financial Data

Cache-Control: no-store

4. Versioned Asset

Cache-Control: public, max-age=31536000, immutable

5. Strong ETag

ETag: "9f8b2c41aa73e0d5"

6. Weak ETag

ETag: W/"9f8b2c41aa73e0d5"

7. Conditional Request

If-None-Match: "9f8b2c41aa73e0d5"

8. 304 Response

HTTP/1.1 304 Not Modified
ETag: "9f8b2c41aa73e0d5"

9. Optimistic Concurrency

If-Match: "9f8b2c41aa73e0d5"
# Mismatch โ†’ 412 Precondition Failed

10. Last-Modified Fallback

Last-Modified: Wed, 15 Oct 2025 14:30:00 GMT
If-Modified-Since: Wed, 15 Oct 2025 14:30:00 GMT

Visual

Caching Layers

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  CACHE-CONTROL vs VALIDATORS                 โ”‚
โ”‚                                              โ”‚
โ”‚  Cache-Control:                              โ”‚
โ”‚    "How long is this fresh?"                 โ”‚
โ”‚    "Who may store it?"                       โ”‚
โ”‚    Controls STORAGE and FRESHNESS.           โ”‚
โ”‚                                              โ”‚
โ”‚  ETag / Last-Modified:                       โ”‚
โ”‚    "Has this changed?"                       โ”‚
โ”‚    Controls VALIDATION.                      โ”‚
โ”‚                                              โ”‚
โ”‚  Fresh response:                             โ”‚
โ”‚    No request to origin.                     โ”‚
โ”‚    Cache-Control is enough.                  โ”‚
โ”‚                                              โ”‚
โ”‚  Stale response:                             โ”‚
โ”‚    Revalidate with ETag/Last-Modified.       โ”‚
โ”‚    304 โ†’ reuse. 200 โ†’ replace.               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Conditional Request Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  VALIDATION FLOW                             โ”‚
โ”‚                                              โ”‚
โ”‚  Client stores:                              โ”‚
โ”‚    ETag: "abc123"                            โ”‚
โ”‚    Last-Modified: Wed, 15 Oct 14:30 GMT      โ”‚
โ”‚                                              โ”‚
โ”‚  Next request:                               โ”‚
โ”‚    If-None-Match: "abc123"                   โ”‚
โ”‚    If-Modified-Since: Wed, 15 Oct 14:30 GMT  โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  Server compares:                            โ”‚
โ”‚    ETag matches? โ†’ 304 Not Modified          โ”‚
โ”‚    ETag differs? โ†’ 200 OK + new body         โ”‚
โ”‚                                              โ”‚
โ”‚  ETag takes precedence over Last-Modified.   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Strong vs Weak ETag

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ETAG TYPES                                  โ”‚
โ”‚                                              โ”‚
โ”‚  Strong: ETag: "abc123"                      โ”‚
โ”‚    Byte-for-byte identical.                  โ”‚
โ”‚    Required for If-Match (concurrency).      โ”‚
โ”‚    Required for byte-range requests.         โ”‚
โ”‚                                              โ”‚
โ”‚  Weak: ETag: W/"abc123"                      โ”‚
โ”‚    Semantically equivalent.                  โ”‚
โ”‚    Bytes may differ (gzip, field order).     โ”‚
โ”‚    NOT allowed for If-Match.                 โ”‚
โ”‚                                              โ”‚
โ”‚  Compression middleware:                     โ”‚
โ”‚    May rewrite strong โ†’ weak.                โ”‚
โ”‚    Causes If-Match failures behind proxy.    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Invalidation Decision

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  WHEN DATA CHANGES                           โ”‚
โ”‚                                              โ”‚
โ”‚  Time-based (max-age):                       โ”‚
โ”‚    Data stale after TTL.                     โ”‚
โ”‚    Simple. May serve stale.                  โ”‚
โ”‚                                              โ”‚
โ”‚  Event-based (invalidation):                 โ”‚
โ”‚    Purge cache on write.                     โ”‚
โ”‚    Precise. Requires infrastructure.         โ”‚
โ”‚                                              โ”‚
โ”‚  Version-based (ETag bump):                  โ”‚
โ”‚    New version โ†’ new ETag.                   โ”‚
โ”‚    Client revalidates, gets 200.             โ”‚
โ”‚                                              โ”‚
โ”‚  Hybrid: max-age as fallback +               โ”‚
โ”‚  event invalidation on critical writes.      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Cache-ControlControls freshness and storage scope
ETagOpaque validator, primary
Last-ModifiedTimestamp validator, fallback
Strong ETagByte-for-byte, required for If-Match
Weak ETagSemantic equivalence, W/ prefix
Conditional requestIf-None-Match / If-Modified-Since
Match response304 Not Modified, no body
Mismatch response200 OK, new body + validators
privateBrowser-only cache
no-storeNever cache

Key takeaways:

  • Cache-Control and validators solve different problems. Cache-Control controls freshness and storage scope. ETag and Last-Modified control validation after freshness expires. A response without validators cannot be revalidated; a response without Cache-Control is revalidated on every request .
  • private prevents cross-user leaks. Authenticated endpoints that return user-specific data must use Cache-Control: private to prevent CDNs and shared proxies from serving one user’s data to another .
  • Strong ETags are the default; weak ETags are for variant representations. Strong ETags promise byte equality and are required for If-Match concurrency control. Weak ETags promise semantic equivalence and are not allowed for If-Match .
  • Last-Modified is a fallback, not a primary validator. Its one-second granularity and clock-skew susceptibility make it less reliable than ETag. Send both; clients use ETag when available and fall back to Last-Modified for HTTP/1.0 compatibility .
  • If-Match prevents lost updates. Two clients editing the same resource can overwrite each other. If-Match with the current ETag makes the update conditional: if the ETag has changed, the server returns 412 Precondition Failed instead of applying a stale update .
  • Invalidation is the hard part. Time-based max-age is simple but may serve stale data. Event-based invalidation is precise but requires infrastructure. The hybrid approach uses max-age as a fallback and triggers invalidation on critical writes .

Remember: Caching is a contract in two parts. Cache-Control tells caches how long they may serve a response without asking the origin. Validators tell caches how to ask whether the response has changed. The first saves requests entirely; the second saves bandwidth when the answer is “nothing changed.” Use private for user data, no-store for secrets, and public with s-maxage for reference data. Generate strong ETags from a content hash or a version column, send Last-Modified alongside for compatibility, and support If-Match on writes to prevent lost updates. The headers are small; the difference between a correctly cached API and a misconfigured one is measured in bandwidth, latency, and data leaks.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!