Leverage Browser Caching: Set Long Cache Lifetimes
Without proper cache headers, browsers re-download the same files on every visit. Learn how to set cache policies that dramatically speed up repeat visits.
When a returning visitor loads your page, the browser can skip downloading files it already has — but only if you tell it to. Without proper cache headers, every visit is like the first visit.
Why Browser Caching Matters
- Repeat visits are instant — cached CSS, JS, and images load from disk in <5ms
- Saves bandwidth — users don't re-download unchanged files
- Reduces server load — fewer requests to your origin server
- Lighthouse audit — flags assets with cache lifetimes under 1 year
How Caching Works
First visit:
Browser → Server: GET /styles.abc123.css
Server → Browser: 200 OK + Cache-Control: max-age=31536000
Repeat visit:
Browser: "I have /styles.abc123.css cached for 1 year"
Browser: (uses cached version, no network request)
The Cache Strategy
Static Assets (JS, CSS, Fonts, Images)
Use long-lived caches with content-based filenames:
Cache-Control: public, max-age=31536000, immutable
This requires filename hashing so old files are never served:
styles.abc123.css → updated to → styles.xyz789.css
main.def456.js → updated to → main.uvw012.js
HTML Pages
Don't cache HTML for long — it needs to reference the latest asset filenames:
Cache-Control: public, no-cache
Or for CDN caching with quick updates:
Cache-Control: public, s-maxage=60, stale-while-revalidate=3600
How to Set Cache Headers
Nginx
# Static assets — cache for 1 year
location ~* \.(js|css|png|jpg|jpeg|webp|avif|gif|svg|woff2|ico)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Vary "Accept-Encoding";
}
# HTML — revalidate every time
location ~* \.html$ {
add_header Cache-Control "public, no-cache";
}
Apache (.htaccess)
<IfModule mod_expires.c>
ExpiresActive On
# Static assets — 1 year
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/avif "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
# HTML — no cache
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
Next.js (Vercel)
Next.js automatically sets optimal cache headers:
/_next/static/*→Cache-Control: public, max-age=31536000, immutable- Pages →
Cache-Control: s-maxage=1, stale-while-revalidate
For custom headers in next.config.js:
module.exports = {
async headers() {
return [
{
source: "/images/:path*",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
];
},
};
Express.js
import express from "express";
const app = express();
// Static assets with long cache
app.use("/static", express.static("public", {
maxAge: "1y",
immutable: true,
}));
// API responses — don't cache
app.use("/api", (req, res, next) => {
res.set("Cache-Control", "no-store");
next();
});
Filename Hashing
The key to safe long-lived caching is content-based filenames. When the content changes, the filename changes:
Webpack (automatic)
module.exports = {
output: {
filename: "[name].[contenthash].js",
assetModuleFilename: "[name].[contenthash][ext]",
},
};
Vite (automatic by default)
Vite adds content hashes to all built assets automatically.
Next.js (automatic)
Next.js hashes all static assets under /_next/static/ automatically.
Verifying Cache Headers
Browser DevTools
- Open DevTools → Network
- Click any static resource
- Check Response Headers for
cache-control
Command Line
curl -I https://yoursite.com/_next/static/css/abc123.css \
| grep -i cache-control
# Expected: cache-control: public, max-age=31536000, immutable
Common Mistakes
Caching Without Filename Hashing
❌ Cache-Control: max-age=31536000 on "styles.css"
→ Users get stale CSS for a year after updates!
✅ Cache-Control: max-age=31536000 on "styles.abc123.css"
→ Safe — filename changes when content changes
Using ETag Without max-age
ETags still require a network round-trip to revalidate. Use max-age to avoid the round-trip entirely.
Caching API Responses
Dynamic data should use no-store or short max-age with revalidation.
Quick Wins Checklist
- Check cache headers on all static assets (DevTools → Network)
- Set
max-age=31536000, immutableon fingerprinted assets - Verify your build tool adds content hashes to filenames
- Set
no-cacheon HTML pages - Set
no-storeon API responses with user-specific data - Configure CDN caching rules (s-maxage for edge caching)
Ready to stop wasting ad spend?
Track your landing page performance, monitor Core Web Vitals, and calculate exactly how much slow pages cost you.
Start Free — No Credit Card