Detailed guide

Caching Headers Optimization Guide

View contents

Complete Guide to HTTP Caching Headers Configuration for Web Performance

Introduction

HTTP caching is one of the most powerful performance optimizations available. Properly configured caching can eliminate network requests entirely, reducing page load times from seconds to milliseconds for returning visitors. This guide provides comprehensive implementation strategies for different server environments.

New to caching? Start with our beginner-friendly guide: 📖 Read: Caching Headers Explained

Strategy 1: Cache-Control Directives Deep Dive

Complete Directive Reference

Cache-Control Directives:
┌─────────────────────────────────────────────────────────────┐
│ FRESHNESS DIRECTIVES                                        │
│ ├── max-age=<seconds>      How long cache is fresh          │
│ ├── s-maxage=<seconds>     CDN/proxy cache duration         │
│ └── stale-while-revalidate=<seconds>  Serve stale while     │
│                            fetching fresh copy              │
│                                                             │
│ REVALIDATION DIRECTIVES                                     │
│ ├── no-cache               Always revalidate before using   │
│ ├── must-revalidate        Revalidate after max-age expires │
│ └── proxy-revalidate       Proxies must revalidate          │
│                                                             │
│ STORAGE DIRECTIVES                                          │
│ ├── no-store               Never cache (sensitive data)     │
│ ├── private                Only browser can cache           │
│ └── public                 CDNs and proxies can cache       │
│                                                             │
│ OPTIMIZATION DIRECTIVES                                     │
│ └── immutable              Never revalidate (versioned      │
│                            assets)                          │
└─────────────────────────────────────────────────────────────┘

Combining Directives

# Versioned static assets (CSS, JS with hash in filename)
Cache-Control: public, max-age=31536000, immutable

# HTML pages (always check for updates)
Cache-Control: no-cache

# Sensitive user data (never cache)
Cache-Control: no-store, private

# API responses (short cache with background refresh)
Cache-Control: public, max-age=60, stale-while-revalidate=600

# CDN-specific longer cache
Cache-Control: public, max-age=3600, s-maxage=86400

Strategy 2: Server Configuration

Nginx Configuration

# /etc/nginx/conf.d/caching.conf

# Versioned static assets - cache forever
location ~* \.(css|js)$ {
    # Only if filename contains hash (e.g., app.abc123.js)
    if ($uri ~* ".*\.[a-f0-9]{8,}\.") {
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    # Non-versioned fallback
    if ($uri !~* ".*\.[a-f0-9]{8,}\.") {
        add_header Cache-Control "public, max-age=86400";
    }
    add_header Vary "Accept-Encoding";
}

# Images and fonts - long cache
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|woff|woff2|ttf|eot)$ {
    add_header Cache-Control "public, max-age=31536000";
    add_header Vary "Accept-Encoding";
}

# HTML - always revalidate
location ~* \.html$ {
    add_header Cache-Control "no-cache";
    add_header Vary "Accept-Encoding";
}

# API endpoints - short cache with stale-while-revalidate
location /api/ {
    add_header Cache-Control "public, max-age=60, stale-while-revalidate=600";
    add_header Vary "Accept-Encoding, Authorization";
}

# Enable ETag
etag on;

Apache Configuration

# .htaccess or httpd.conf

<IfModule mod_expires.c>
    ExpiresActive On

    # Default: 1 month
    ExpiresDefault "access plus 1 month"

    # HTML: always revalidate
    ExpiresByType text/html "access plus 0 seconds"

    # CSS/JS: 1 year (use versioned filenames)
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"

    # Images: 1 year
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"

    # Fonts: 1 year
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

<IfModule mod_headers.c>
    # Versioned assets (files with hash in name)
    <FilesMatch "\.[a-f0-9]{8,}\.(css|js)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>

    # HTML files
    <FilesMatch "\.html$">
        Header set Cache-Control "no-cache"
    </FilesMatch>

    # Enable ETag
    FileETag MTime Size
</IfModule>

Node.js/Express Configuration

const express = require('express');
const path = require('path');

const app = express();

// Custom caching middleware
const setCacheHeaders = (req, res, next) => {
  const url = req.url;

  // Versioned assets (contain hash)
  if (/\.[a-f0-9]{8,}\.(css|js)$/.test(url)) {
    res.set('Cache-Control', 'public, max-age=31536000, immutable');
  }
  // Images and fonts
  else if (/\.(jpg|jpeg|png|gif|webp|svg|woff2?|ttf|eot)$/.test(url)) {
    res.set('Cache-Control', 'public, max-age=31536000');
  }
  // HTML
  else if (/\.html$/.test(url) || url === '/') {
    res.set('Cache-Control', 'no-cache');
  }
  // API responses
  else if (url.startsWith('/api/')) {
    res.set('Cache-Control', 'public, max-age=60, stale-while-revalidate=600');
  }

  next();
};

app.use(setCacheHeaders);

// Serve static files with ETags
app.use(express.static('public', {
  etag: true,
  lastModified: true,
  maxAge: 0, // Let middleware handle Cache-Control
}));

Strategy 3: Asset Versioning (Cache Busting)

Build Tool Configuration

Webpack

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'assets/[name].[contenthash:8][ext]',
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
    }),
  ],
};

Vite

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },
  },
});

Why Versioning Matters

Without Versioning:
├── User visits site, downloads styles.css
├── You update styles.css
├── User revisits - browser serves cached (old) styles.css
├── User sees broken layout until cache expires
└── Bad user experience!

With Versioning:
├── User visits site, downloads styles.abc123.css
├── You update CSS, build creates styles.def456.css
├── User revisits - browser requests styles.def456.css (new!)
├── User always sees latest styles
└── Great user experience!

Strategy 4: CDN Caching Configuration

Cloudflare

Page Rules:
URL: example.com/assets/*
Settings:
  - Cache Level: Cache Everything
  - Edge Cache TTL: 1 month
  - Browser Cache TTL: 1 year

URL: example.com/*.html
Settings:
  - Cache Level: Standard
  - Edge Cache TTL: Respect Existing Headers

AWS CloudFront

{
  "CacheBehaviors": [
    {
      "PathPattern": "/assets/*",
      "DefaultTTL": 31536000,
      "MaxTTL": 31536000,
      "MinTTL": 31536000,
      "Compress": true
    },
    {
      "PathPattern": "*.html",
      "DefaultTTL": 0,
      "MaxTTL": 0,
      "MinTTL": 0
    }
  ]
}

Vercel

// vercel.json
{
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    },
    {
      "source": "/(.*).html",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "no-cache"
        }
      ]
    }
  ]
}

Strategy 5: Cache Invalidation

Strategies Comparison

Strategy How It Works Pros Cons
Versioned URLs Change filename Instant, reliable Requires build step
Query strings ?v=123 Easy to implement Some CDNs ignore
CDN purge API call to CDN Flexible Slow, may miss edges
Short TTL Low max-age Simple More requests
Best Practice Cache Invalidation:
┌─────────────────────────────────────────────────────────────┐
│ Static Assets (CSS, JS, Images):                            │
│ └── Use content-hash in filename (automatic invalidation)   │
│                                                             │
│ HTML Pages:                                                 │
│ └── Use no-cache (always revalidate)                        │
│                                                             │
│ API Responses:                                              │
│ └── Use stale-while-revalidate for background refresh       │
│                                                             │
│ Emergency Updates:                                          │
│ └── CDN purge API + deploy new versioned assets             │
└─────────────────────────────────────────────────────────────┘

Strategy 6: Vary Header for Conditional Caching

When to Use Vary

# Cache different versions based on encoding
Vary: Accept-Encoding

# Cache different versions for different languages
Vary: Accept-Language

# Cache different for authenticated vs anonymous users
Vary: Authorization

# Multiple conditions
Vary: Accept-Encoding, Accept-Language

Nginx Vary Configuration

# Add Vary header for compressed content
location ~* \.(css|js|html|json|xml)$ {
    add_header Vary "Accept-Encoding";
    gzip on;
}

# Add Vary for language-specific content
location /content/ {
    add_header Vary "Accept-Language";
}

Measuring Cache Effectiveness

Before/After Comparison

BEFORE Cache Optimization:
├── Repeat Visit Page Load: 2.8s
├── Transferred Data: 1.9 MB
├── Server Requests: 42
├── Cache Hit Rate: 15%
└── Lighthouse Performance: 68

AFTER Cache Optimization:
├── Repeat Visit Page Load: 0.6s (↓79%)
├── Transferred Data: 85 KB (↓96%)
├── Server Requests: 8 (↓81%)
├── Cache Hit Rate: 94%
└── Lighthouse Performance: 96 (↑28 points)

Lighthouse Audits

Check for these results:

  • “Serve static assets with an efficient cache policy” - Should pass
  • Shows any resources with cache TTL < 1 week

Testing with DevTools

Network Tab Analysis:
1. Enable "Disable cache" checkbox
2. Load page (simulates first visit)
3. Uncheck "Disable cache"
4. Reload page (simulates repeat visit)
5. Compare "Size" column:
   - "(disk cache)" = cached locally
   - "(memory cache)" = cached in memory
   - Actual size = downloaded from network

Optimization Checklist

Before deploying, verify:

  • [ ] Versioned static assets use max-age=31536000, immutable
  • [ ] HTML uses no-cache for always-fresh content
  • [ ] Images/fonts use max-age=31536000
  • [ ] ETags enabled for efficient revalidation
  • [ ] Vary: Accept-Encoding set for compressed content
  • [ ] CDN caching properly configured
  • [ ] Build tool generates content hashes in filenames
  • [ ] API responses use appropriate caching strategy
  • [ ] Sensitive data uses no-store
  • [ ] Lighthouse caching audit passes

Continue optimizing your resource delivery:

📚 Back to Performance SEO Hub - Explore all performance topics


References

  1. MDN Web Docs - HTTP Caching
  2. web.dev - HTTP Cache
  3. Chrome Developers - Serve Static Assets with Efficient Cache Policy
  4. Google Developers - HTTP Caching

Try It Yourself

Ready to optimize your caching headers?

🔧 Download UXR SEO Analyzer (Free, 100% local analysis)


Disclaimer: The analyzers in this extension are reference guides based on official documentation from MDN, web.dev, and Chrome Developers. They do not represent absolute truths about how search engines evaluate your content—only search engines know their internal algorithms. Use these recommendations as a starting point to improve your site.

Last updated: December 15, 2025

Related articles

Related version

Introduction

Caching Headers Explained

HTTP caching allows browsers to store copies of resources locally, eliminating the need to download them again on subsequent visits

Category hub

Hub

Performance Seo Hub

Performance is a critical ranking factor and directly impacts user experience

In the same category

Detailed guide

Ttfb Optimization Guide

Time to First Byte (TTFB) is the foundation of web performance—every millisecond of TTFB delays your entire page load

Detailed guide

Text Compression Implementation Guide

Text compression can reduce transfer sizes by 60-90%, making it one of the highest-impact performance optimizations available

Detailed guide

Render Blocking Resources Optimization Guide

Render-blocking resources are one of the most impactful performance issues you can fix