Caching Strategies — Browser, CDN, and Server-Side Explained
In this tutorial, you will learn how to implement caching at three levels, browser, CDN edge, and server-side, to dramatically reduce page load times and backend load. Caching is the single most impactful performance optimization because it avoids redundant network requests and computation. DodaZIP uses aggressive caching for its download assets, serving over 80 percent of requests directly from edge caches.
What You Will Learn
- How to configure browser caching with Cache-Control and ETag headers
- How to set up a CDN cache layer with purge strategies
- How to implement server-side caching with Redis and Memcached
- How to avoid common caching pitfalls like stale content and cache poisoning
Why It Matters
Every millisecond of latency costs conversions. A well-implemented caching strategy can reduce Time to First Byte (TTFB) by 60 to 80 percent and cut origin server load by an order of magnitude. DodaTech serves millions of requests daily; without caching the infrastructure cost would be prohibitive.
Real-World Use Case
The Doda Browser extension update mechanism uses a three-tier cache: the service worker cache on the client, a Cloudflare CDN cache at the edge, and a Redis cache on the origin server. Update checks complete in under 50 milliseconds for 90 percent of users.
Prerequisites
You should understand HTTP request-response semantics, how DNS resolution works, and basic NGINX or Apache configuration. Familiarity with Redis is helpful for the server-side section.
Step-by-Step Tutorial
Step 1: Configure Browser Caching Headers
Browser caching stores static assets locally so repeat visits load instantly. Use Cache-Control headers to define caching policies.
# NGINX configuration for browser caching
location /static/ {
expires 365d;
add_header Cache-Control "public, immutable";
}
location /images/ {
expires 30d;
add_header Cache-Control "public";
}
Expected behavior: After the first visit, static assets are loaded from the local disk cache instead of the network. The Network tab in DevTools shows "(disk cache)" for these requests.
Step 2: Implement ETags for Validation
ETags provide cache validation without downloading the entire file. The browser sends a conditional request with the ETag value, and the server responds with 304 Not Modified if the content has not changed.
// Express.js ETag setup
const express = require('express');
const app = express();
app.set('etag', 'strong'); // Use strong ETag (hash-based)
app.use('/static', express.static('public', {
etag: true,
lastModified: true,
maxAge: '30d'
}));
Expected output: Response headers include ETag: "abc123". Subsequent requests include If-None-Match: "abc123" and receive a 304 response with zero bytes.
Step 3: Set Up CDN Caching
CDN caching stores content at edge locations close to users. This section uses Cloudflare as an example.
# Using Cloudflare API to set page rule cache settings
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/pagerules" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{
"targets": [{"target": "url", "constraint": {"operator": "matches", "value": "*dodatech.com/static/*"}}],
"actions": [{"id": "cache_level", "value": "cache_everything"}]
}'
Expected result: Static assets are cached at Cloudflare edge nodes. The cf-cache-status: HIT header confirms the asset was served from the edge.
Step 4: Implement Server-Side Caching with Redis
Server-side caching stores computed results to avoid redundant database queries or API calls.
// Node.js Redis caching example
const redis = require('redis');
const client = redis.createClient();
const express = require('express');
const app = express();
async function getUserData(userId) {
const cacheKey = `user:${userId}`;
const cached = await client.get(cacheKey);
if (cached) return JSON.parse(cached);
// Simulate expensive database query
const data = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
await client.setEx(cacheKey, 3600, JSON.stringify(data));
return data;
}
app.get('/api/user/:id', async (req, res) => {
const data = await getUserData(req.params.id);
res.json(data);
});
Expected behavior: First request hits the database and takes 200ms. Subsequent requests within the TTL hit Redis and take under 5ms.
Step 5: Create a Cache Invalidation Strategy
Caching is useless without a proper invalidation strategy. Common approaches include:
- Time-based expiration (TTL)
- Event-based purging (purge cache when content updates)
- Versioned URLs (cache-busting with hash in filename)
# Purge Cloudflare cache programmatically
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{"files": ["https://dodatech.com/static/main.css"]}'
Step 6: Implement Service Worker Caching
For progressive web apps, a service worker provides an additional caching layer that works offline.
// Service worker install event - precache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/',
'/static/main.css',
'/static/app.js',
'/static/logo.webp'
]);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => {
return cached || fetch(event.request);
})
);
});
Expected behavior: After the service worker installs, the app loads from cache even when offline.
Learning Path
flowchart LR A[Core Web Vitals] --> B[Caching Strategies] B --> C[CDN Configuration] B --> D[Reduce TTFB] C --> E[HTTP/2 and HTTP/3] D --> E style B fill:#4f46e5,color:#fff style A fill:#6366f1,color:#fff style C fill:#6366f1,color:#fff
Common Errors
Setting a short TTL on all assets: Static assets like fonts and logos rarely change. Use a one-year TTL with cache-busting hashed filenames.
Caching authenticated content without care: Never cache personalized content with public Cache-Control. Use
private, no-cachefor authenticated responses.Ignoring cache headers from the origin: Your CDN respects origin cache headers by default. If the origin sends
Cache-Control: no-cache, the CDN will not cache.Forgetting to purge cached data after updates: Deploying new CSS or JavaScript without updating filenames or purging the cache leads to stale assets for users.
Over-caching API responses: Aggressively caching API responses can serve stale data. Use short TTLs with stale-while-revalidate for frequently changing data.
No cache key variation for mobile vs desktop: CDNs cache by URL by default. Serve different content to mobile and desktop users without varying the cache key, causing wrong content delivery.
Practice Questions
- What is the difference between Cache-Control: public and private?
- How does an ETag improve cache efficiency?
- What is cache busting and why is it needed?
- Describe a three-tier caching architecture.
- How does
stale-while-revalidatework?
Answers: 1. public allows any cache to store the response; private restricts caching to the browser only. 2. ETags allow the server to return 304 Not Modified without re-downloading the body. 3. Cache busting uses unique filenames (via hash) to force browsers to fetch new versions when content changes. 4. Browser cache, CDN edge cache, and origin server cache (Redis). 5. It serves stale content immediately while fetching fresh content in the background, then updates the cache for the next request.
Challenge
Build a caching proxy using Node.js that sits between the client and a slow API. Cache responses in Redis with a 60-second TTL, implement ETag-based conditional requests, and set Cache-Control headers appropriate for public API responses.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro