A smart HTTP caching strategy that combines aggressive caching with guaranteed content freshness.
Lucid caching is a HTTP cache control strategy that uses the following header combination for HTML files:
Cache-Control: public, max-age=31536000, no-cache
This creates a "lucid" (clear, transparent) caching behavior that gives you both aggressive performance and guaranteed freshness.
The Magic Combination:
public
- Content can be cached by browsers and CDNsmax-age=31536000
- Cache for 1 year (aggressive caching)no-cache
- Always revalidate with server before serving
- First request: Browser downloads and caches HTML with 1-year expiry
- Subsequent requests: Browser always asks server "Has this changed?"
- If unchanged: Server responds
304 Not Modified
, browser uses cached version - If changed: Server sends new content, browser updates cache
Why This Is Effective:
- Near-instant loading - Browser has content cached for up to 1 year
- Always current - Content is validated on every request
- Minimal bandwidth - 304 responses are tiny (just headers)
- Zero stale content - Impossible to serve outdated HTML
- Developer friendly - No cache-busting URLs needed for HTML
import { serve, file } from "bun";
// Spin up a static file server
const server = serve({
port: 8080,
// SPA-aware fetch handler with smart caching
async fetch(req) {
try {
const url = new URL(req.url);
let pathname = url.pathname;
// SPA route → rewrite to /index.html
const isFileRequest = /\.[^/]+$/.test(pathname);
if (!isFileRequest) {
pathname = pathname.replace(/\/?$/, "/index.html");
}
// Missing file → fallback to /index.html
if (!(await file(`./dist${pathname}`).exists())) {
pathname = "/index.html";
}
// Cache-Control: immutable for hashed assets, lucid for HTML
const headers = new Headers();
headers.set(
"Cache-Control",
pathname.match(/-[a-zA-Z0-9]{8,}\./)
? "public, max-age=31536000, immutable"
: "public, max-age=31536000, no-cache",
);
return new Response(file(`./dist${pathname}`), { headers });
} catch (error) {
console.error("❌ Error while serving request:", error);
return new Response("Not Found", { status: 404 });
}
},
});
server {
listen 80;
root /var/www/html;
# Lucid caching for HTML files
location ~* \.html$ {
add_header Cache-Control "public, max-age=31536000, no-cache";
try_files $uri $uri/ /index.html;
}
# Immutable caching for hashed assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
expires 1y;
}
}
Strategy | Header | Performance | Freshness | Use Case |
---|---|---|---|---|
No Cache | no-cache, no-store |
Slow | ✅ Always fresh | Development |
Short Cache | max-age=300 |
Medium | ✅ Mostly fresh | Dynamic content |
Long Cache | max-age=31536000 |
⚡ Fast | ❌ Can be stale | Static assets |
Immutable Cache | max-age=31536000, immutable |
⚡ Instant | ✅ Never changes | Hashed assets |
Stale-While-Revalidate | max-age=300, stale-while-revalidate=86400 |
⚡ Instant | Long sessions | |
🎯 Lucid Cache | max-age=31536000, no-cache |
⚡ Fast | ✅ Always fresh | Regularly updated static sites |
The perfect caching strategy combines Lucid caching for HTML with Immutable caching for static assets:
# HTML files - Always fresh with Lucid caching
Cache-Control: public, max-age=31536000, no-cache
# Hashed assets - Forever cached with immutable
Cache-Control: public, max-age=31536000, immutable
index.html → Lucid caching
about.html → Lucid caching
chunk-a1b2c3.js → Immutable caching
styles-x4y5z6.css → Immutable caching
logo-m7n8o9.png → Immutable caching
- HTML requests → Server validates freshness, serves updated content (always fresh)
- Asset requests → Cached forever until filename/hash changes (maximum performance)
- New deployments → HTML updates with new asset URLs, forcing fresh asset downloads
- Perfect invalidation → HTML controls when assets update (zero stale content, efficient deployments)
Both strategies solve caching problems but with different trade-offs:
Cache-Control: public, max-age=31536000, no-cache
Behavior:
- Every request: Check server first, then serve from cache
- Response time: Fast (validation + cache)
- Freshness: Always current
Cache-Control: public, max-age=3600, stale-while-revalidate=600
Behavior:
- First 60 minutes: Serve from cache instantly
- Next 10 minutes: Serve stale copy, revalidate in background
- After 70 minutes: Must fetch fresh response before serving
- Response time: Instant (cache)
- Freshness: May be stale, depends on timing
Aspect | Lucid Caching | Stale-While-Revalidate |
---|---|---|
Performance | (fast) | (instant) |
Freshness | Always current | Potentially stale |
Complexity | Simple | More complex timing |
Predictability | Consistent behavior | Depends on timing |
Server load | Validation on every request | Validation + background requests |
Choose Lucid Caching when:
- Content changes frequently (daily/weekly deploys)
- Immediate freshness is critical
- You want predictable behavior
- Simplicity is important
Choose Stale-While-Revalidate when:
- Performance is more critical than immediate freshness
- Content changes infrequently
- Users browse in longer sessions
- You can tolerate temporary staleness
The term "lucid" means clear, transparent, and easily understood. Lucid caching creates a transparent caching behavior where:
- Clear performance - Fast subsequent loads
- Transparent freshness - Always current content
- Easily understood - Simple header combination with predictable behavior
It's the clarity of getting both performance and freshness without the typical trade-offs.
Lucid Caching by Ezekiel Chentnik
Making HTTP caching both fast and fresh, one header at a time.