git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/perf-browser-cache" ~/.claude/skills/intense-visions-harness-engineering-perf-browser-cache && rm -rf "$T"
agents/skills/claude-code/perf-browser-cache/SKILL.mdBrowser Caching
Master HTTP browser caching — Cache-Control directives, ETag and Last-Modified validation, immutable assets with content-hashed filenames, stale-while-revalidate patterns, and cache partitioning in modern browsers for optimal repeat-visit performance.
When to Use
- Repeat visitors experience slow page loads despite no content changes
- Lighthouse flags "Serve static assets with an efficient cache policy"
- DevTools Network panel shows 200 responses instead of 304 or (disk cache) for static assets
- You need to decide between no-cache, no-store, and max-age for different content types
- A deploy invalidates all cached assets unnecessarily (cache busting is too aggressive)
- Content-hashed filenames are not implemented and versioned assets are served with short TTLs
- Stale content is being served after deploys because cache invalidation is not working
- You need to understand cache partitioning impact on shared CDN-hosted resources
- API responses could benefit from conditional caching with ETags
- Cache hit rates on repeat visits are below 90%
Instructions
-
Audit current caching behavior. In Chrome DevTools Network panel, check the "Size" column. Values like "(disk cache)" or "(memory cache)" indicate cached resources. Look at response headers for Cache-Control directives. Resources without caching headers or with
are fetched fresh every time.no-store -
Design a caching strategy by content type. Different content types need different caching policies:
Static assets (JS, CSS, images) with content hash: Cache-Control: public, max-age=31536000, immutable HTML documents: Cache-Control: no-cache (or: Cache-Control: public, max-age=0, must-revalidate) API responses (cacheable): Cache-Control: private, max-age=60, stale-while-revalidate=300 User-specific data: Cache-Control: private, no-cache Sensitive data (banking, health): Cache-Control: no-store -
Implement content-hashed filenames. Content hashing ensures cached files are automatically invalidated when content changes:
# Build output with content hash app.a1b2c3d4.js ← hash changes when code changes style.e5f6g7h8.css ← hash changes when styles change vendor.i9j0k1l2.js ← hash rarely changes (stable dependencies) # Cache-Control for hashed files Cache-Control: public, max-age=31536000, immutable # 1 year TTL + immutable = browser never revalidatesThe HTML document (not content-hashed) references specific hashed filenames. When you deploy, the HTML changes to reference new hashes, and the browser fetches the new files.
-
Configure ETag validation for dynamic content. ETags allow the browser to check if content has changed without re-downloading:
# First request GET /api/products → 200 OK → ETag: "abc123" → Cache-Control: no-cache # Subsequent request (browser sends conditional request) GET /api/products If-None-Match: "abc123" → 304 Not Modified (no body, saves bandwidth) -
Use stale-while-revalidate for balanced freshness. This directive serves the cached version immediately while fetching a fresh copy in the background:
Cache-Control: public, max-age=3600, stale-while-revalidate=86400 Timeline: 0-1h: Serve from cache (fresh) 1h-25h: Serve from cache (stale) + revalidate in background >25h: Must revalidate before serving (cache expired) -
Understand browser cache layers. Browsers maintain multiple cache tiers:
- Memory cache — fastest, cleared on tab close, used for preloaded resources and recently accessed items
- Disk cache — persists across sessions, used for most HTTP-cached resources
- Service Worker cache — application-controlled via Cache API
- Push cache — HTTP/2 push cache, cleared after connection closes
-
Account for cache partitioning. Modern browsers (Chrome 86+, Firefox 85+) partition the HTTP cache by top-level site. A resource cached when visiting
is NOT reused when visitingsite-a.com
, even if the URL is identical. This impacts shared CDN-hosted resources (Google Fonts, cdnjs, unpkg) — self-hosting eliminates the cross-site cache miss.site-b.com
Details
Cache-Control Directive Reference
| Directive | Meaning |
|---|---|
| Any cache (browser, CDN, proxy) may store this response |
| Only the end-user browser may cache (not CDN or proxy) |
| Cache is fresh for N seconds from response time |
| CDN/proxy TTL (overrides max-age for shared caches) |
| Cache the response but revalidate before every use |
| Do not cache at all — not in memory, not on disk |
| After max-age expires, MUST revalidate (no stale serving) |
| Content will never change — skip revalidation entirely |
| Serve stale for N seconds while revalidating in background |
| Serve stale for N seconds if origin returns an error |
Worked Example: Twitter Static Asset Caching
Twitter serves static assets with
Cache-Control: public, max-age=31536000, immutable using content-hashed filenames (e.g., main.a1b2c3d4.js). This eliminates all revalidation requests on repeat visits — the browser serves directly from disk cache without any network activity. On deploy, the HTML document (cached with no-cache) references new hashed filenames, triggering fresh downloads only for changed assets. Unchanged assets (vendor libraries, shared components) remain cached. This approach achieves >99% cache hit rate for static assets on repeat visits.
Worked Example: Financial Times Stale-While-Revalidate
The Financial Times implemented
stale-while-revalidate with a 1-hour fresh window and 24-hour stale window for their article pages. Result: 95% of page views are served instantly from cache (either fresh or stale), with background revalidation keeping content within 1 hour of the latest version. For breaking news, they supplement with explicit cache purging via the CDN API. This approach reduced perceived page load time by 70% on repeat visits compared to their previous no-cache policy.
Anti-Patterns
Using no-cache when you mean no-store.
no-cache DOES cache the response — it just requires revalidation before each use (a conditional request). no-store prevents caching entirely. For truly sensitive data (banking details, health records), use no-store. For HTML that should always be fresh, use no-cache (allows 304 responses).
Setting short max-age on versioned assets. If filenames contain content hashes, the content at that URL will never change. Setting
max-age=3600 on app.a1b2c3.js forces unnecessary revalidation every hour. Use max-age=31536000, immutable instead.
Forgetting Vary header with content negotiation. If your server returns different content based on
Accept-Encoding or Accept-Language, the Vary header must list those headers. Without it, a cache may serve a gzip-compressed response to a client that sent Accept-Encoding: br.
Query string cache busting instead of filename hashing. URLs like
/app.js?v=123 rely on all caches respecting query parameters. Some CDNs strip or ignore query strings by default. Content-hashed filenames (/app.abc123.js) are universally reliable.
Source
- MDN Web Docs: HTTP Caching — https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
- RFC 9111: HTTP Caching — https://www.rfc-editor.org/rfc/rfc9111
- web.dev: HTTP Cache — https://web.dev/articles/http-cache
- Chrome Cache Partitioning — https://developer.chrome.com/blog/http-cache-partitioning/
- Jake Archibald: "Caching best practices" — https://jakearchibald.com/2016/caching-best-practices/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
- Static assets with content hashes use
caching.max-age=31536000, immutable - HTML documents use
or equivalent to ensure fresh content on navigation.no-cache - Repeat visit network activity shows (disk cache) for static assets instead of 200 responses.