算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
frontend-browser-caching【设计与创意】Use this skill when the user says 'browser caching', 'cache headers', 'service worker', 'SW', 'stale-while-revalidate', 'SWR', 'cache strategy', 'Cache-Control', 'ETag', 'cache busting', 'offline caching', or when optimizing frontend load performance. This skill enforces: Cache-Control headers with appropriate max-age, service worker caching with sta…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Optimize load performance via HTTP cache headers, service worker caching, and stale-while-revalidate. Minimize network round-trips while serving fresh content. First load under 2s, repeat loads under 500ms. Full offline support for static assets.
Exact user phrases: "browser caching", "cache headers", "service worker", "SW", "stale-while-revalidate", "SWR", "cache strategy", "Cache-Control", "ETag", "cache busting", "offline caching".
Before activating, verify:
No file output. Produces caching strategy, header configs, or service worker code as text.
Resource: {type}
Strategy: {cache strategy}
Config: {code block}
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output.
Cache-Control: public, max-age=31536000, immutable.Cache-Control: no-cache for freshness validation.Cache-Control or ETag.4096 tokens.
Resource type?
|-- Static asset (JS, CSS, image, font) -->
| |-- Content-hashed filename?
| | |-- YES --> Cache-Control: public, max-age=31536000, immutable
| | |-- NO --> Add content hashing to build tool
|-- HTML page -->
| |-- Static / pre-rendered? --> Cache-Control: public, max-age=300 (5 min), ETag
| |-- Dynamic / user-specific? --> Cache-Control: no-cache, private
|-- API response -->
| |-- Public / shareable? --> Cache-Control: public, max-age=60, stale-while-revalidate=600
| |-- User-specific? --> Cache-Control: private, no-cache
|-- Third-party resource -->
|-- CDN-hosted? --> Use the CDN's cache headers, add integrity hash
Where should the response be cached?
|-- Browser (private cache) -->
| |-- Static asset: Cache-Control: private, max-age=31536000
| |-- Auth response: Cache-Control: private, no-cache
|-- CDN / proxy (shared cache) -->
| |-- Public page: Cache-Control: public, s-maxage=3600, max-age=60
| |-- API response: Cache-Control: public, s-maxage=300
|-- Service worker -->
|-- Install-time: Precache all versioned assets
|-- Runtime: Stale-while-revalidate for API + HTML
Content changed — how to invalidate?
|-- Static asset changed -->
| |-- Content hash in URL? --> New URL = automatic invalidation
| |-- No hash? --> Versioned path (/v2/asset.js) or query string (?v=2)
|-- API response changed -->
| |-- Use ETag? --> Conditional request validates freshness
| |-- No ETag? --> Short max-age or no-cache
|-- HTML page changed -->
|-- Use revalidation? --> no-cache + ETag = instant freshness check
|-- Use CDN? --> Purge CDN cache for that path
Option A: Cache-first for static assets, network-first for HTML Best for: Content-heavy sites where assets change rarely. Provides offline capability.
Install: precache all static assets
Fetch: serve from cache, update in background (stale-while-revalidate)
HTML: network first, fallback to cache
API: stale-while-revalidate
Option B: Network-first with cache fallback Best for: Dynamic apps where freshness is critical (dashboards, admin panels).
Install: precache shell (minimal HTML, CSS, JS)
Fetch: HTML and API always from network
Static assets: cache-first
Fallback: cached offline page
Option C: Offline-first (fully cached) Best for: Progressive Web Apps that must work entirely offline.
Install: precache everything needed for full functionality
Fetch: cache-first for all resources, periodic background sync
API: IndexedDB local cache with background sync when online
# Static assets (JS, CSS, images, fonts)
Cache-Control: public, max-age=31536000, immutable
# HTML pages
Cache-Control: no-cache
ETag: "abc123"
# API responses
Cache-Control: public, max-age=60, stale-while-revalidate=600
# User-specific content
Cache-Control: private, no-cache
const CACHE_NAME = 'static-v1'
const STATIC_ASSETS = [
'/',
'/index.html',
'/app.js',
'/app.css',
'/logo.png',
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
)
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
)
)
)
self.clients.claim()
})
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
const fetchPromise = fetch(event.request).then((response) => {
if (response.ok) {
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, response.clone()))
}
return response
})
return cached ?? fetchPromise
})
)
})
// Vite -- automatic content hashing
// build.rollupOptions.output.entryFileNames: '[name]-[hash].js'
// Webpack
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
}
// Import will resolve to cache-busted URL
import('/assets/app-abc123def.js')
| Resource | Strategy | Cache-Control | SW Behavior |
|---|---|---|---|
| JS/CSS bundles | Cache-first | max-age=31536000, immutable | Pre-cache on install |
| Images | Cache-first | max-age=31536000, immutable | Cache on first fetch |
| HTML pages | Network-first | no-cache | Network with fallback |
| API data | SWR | max-age=60, stale-while-revalidate=600 | Stale-while-revalidate |
| User data | Network-only | private, no-cache | Bypass cache |
<!-- offline.html -->
<!DOCTYPE html>
<html>
<head><title>Offline</title></head>
<body>
<h1>You are offline</h1>
<p>Please check your connection and try again.</p>
<button onclick="location.reload()">Try again</button>
</body>
</html>
// In service worker fetch handler
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.catch(() => caches.match(event.request))
.catch(() => caches.match('/offline.html'))
)
})
// workbox-config.js
module.exports = {
globDirectory: 'dist/',
globPatterns: ['**/*.{js,css,png,jpg,svg,woff2}'],
swDest: 'dist/sw.js',
runtimeCaching: [
{
urlPattern: /\/api\//,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 86400 },
},
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif)$/,
handler: 'CacheFirst',
options: {
cacheName: 'image-cache',
expiration: { maxEntries: 100, maxAgeSeconds: 30 * 86400 },
},
},
],
}
[Browser Request]
|
v
[Service Worker] --(not registered)--> [HTTP Cache]
| |
|-- Cache hit? --> Return cached |-- max-age fresh? --> Return cached
|-- No cache? --> Fetch from network |-- no-cache? --> [Server] (conditional via ETag)
| |
v v
[Network Response] [Network Response]
| |
v v
[Cache Update] [Cache Update]
Modern browsers partition HTTP caches by site (top-level origin + frame origin). This means:
Cache-Control: public, max-age=31536000 without immutable causes browsers to revalidate on every page reload (Cmd+R) even though the asset has a content hash. Always add immutable for content-hashed assets.
Setting max-age on HTML without ETag means users see stale content until the cache expires. Use no-cache with ETag for HTML to ensure freshness while still allowing conditional requests.
Setting Cache-Control: public on authenticated API responses means a shared cache (CDN, proxy) serves the same response to all users. Always use private for user-specific data.
If you update the service worker's cached assets but don't change the cache name (CACHE_NAME), the old cache is not cleaned up and new assets are never cached. Always bump the version in the cache name on each deploy.
// BAD -- same cache name, updates never applied
const CACHE_NAME = 'static-v1';
// GOOD -- versioned cache name
const CACHE_NAME = `static-v${new Date().getTime()}`;
// Or use a build-time constant
// const CACHE_NAME = `static-v${__CACHE_VERSION__}`;
Without skipWaiting() in the install event, the new service worker waits for all tabs of the old service worker to close before activating. This delays updates indefinitely.
The fetch handler caches ALL responses, including 404 and 500 errors. Only cache response.ok responses:
const fetchPromise = fetch(event.request).then((response) => {
if (response.ok) {
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, response.clone()))
}
return response
})
Cross-origin responses without CORS headers are "opaque" — they return status 0 and cannot be inspected with .ok. Cache them anyway for offline support:
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('cdn.example.com')) {
event.respondWith(
caches.match(event.request).then(cached => {
const fetchPromise = fetch(event.request).then(response => {
caches.open(CACHE_NAME).then(cache => cache.put(event.request, response.clone()))
return response
})
return cached || fetchPromise
})
)
}
})
Each origin has limited cache storage (~6% of disk in Chrome). Beyond this, caches.put() throws a QuotaExceededError. Always set maximum cache sizes.
| Strategy | Freshness | Speed | Offline | Complexity |
|---|---|---|---|---|
| Cache-first (immutable) | Stale until expiry | Fastest | Yes | Low |
| Network-first | Always fresh | Slow (network dependent) | Yes | Medium |
| Stale-while-revalidate | Stale served, fresh in background | Very fast (instant) | Yes | Medium |
| Network-only | Always fresh | Slowest | No | Low |
| Cache-only | Stale until expiry | Fastest | Yes | Low |
| Service worker + IndexedDB | Background sync | Fast (local) | Full | High |
Service worker caches can grow unbounded. Set a maximum cache size:
async function trimCache(cacheName, maxItems) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxItems) {
// Delete oldest entries
const toDelete = keys.slice(0, keys.length - maxItems);
await Promise.all(toDelete.map((key) => cache.delete(key)));
}
}
// Call after adding to cache
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request).then((response) => {
if (response.ok) {
const cloned = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, cloned);
trimCache(CACHE_NAME, 50); // Keep max 50 cached entries
});
}
return response;
})
);
});
Browsers limit storage per origin:
Use navigator.storage.estimate() to check available and used storage.
expires and add_header Cache-Control directives.navigator.storage.estimate() shows storage usageSet-Cookie headers in shared caches (CDN, proxy).Cache-Control: private for authenticated content.Clear-Site-Data header can clear cache on logout.immutable + max-age=1 year + content hash in filename.no-cache with ETag -- never cache HTML without revalidation.Cache-Control: private for authenticated responses.updateViaCache: 'none' to prevent SW script caching.references/cache-strategies.md -- Cache Strategiesreferences/caching-headers.md -- Caching Headersreferences/caching-strategies.md -- Caching Strategiesreferences/local-storage-strategies.md -- Local Storage and Cache Strategiesreferences/service-worker-caching.md -- Service Worker Cachingreferences/sw-caching.md -- Service Worker Cachingreferences/service-worker-caching.md -- Service Worker Caching Patternsreferences/cache-invalidation-strategies.md -- Cache Invalidation StrategiesNo artifact produced.
Next skill: performance -- combine caching with other perf optimization.
Carry forward: cache strategy per resource type, SW cache name version, hash config.
` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).
What type of resource are you caching?
├── Static asset (JS, CSS, images) → Cache-First (Service Worker) + long max-age
├── API response → Network-First with stale-while-revalidate
├── HTML pages → Network-First with SW fallback to cache
└── User data → Network-Only (never cache sensitive data)
Need offline support?
├── Yes → Service Worker with Cache-First for critical paths
└── No → HTTP cache headers only (Cache-Control, ETag)
Can the resource change without a version bump?
├── No → Immutable content: Cache-Control: public, max-age=31536000, immutable
└── Yes → Does it have a content hash?
├── Yes → Add hash as URL query or path param
└── No → Use ETag + conditional requests (304 Not Modified)
Need real-time invalidation?
├── Yes → SW message channel + cache busting
└── No → Time-based TTL strategy