Skip to content
⚠️ This article was written in 2019. Some content may be outdated.

Nginx Frontend Cache Strategy Configuration in Practice

Frontend performance optimization often starts with compression and code splitting, but browser caching is another high-leverage area. A proper Nginx caching configuration can dramatically reduce server pressure and significantly improve repeat-visit load times.

Browser Caching Fundamentals

The browser cache decision flow:

Request resource
     ↓
Has local cache?
  ├── No → fetch from server
  └── Yes
        ↓
     Cache expired?
       ├── No → use local cache (200 from cache)
       └── Yes
             ↓
          Send conditional request (If-None-Match / If-Modified-Since)
            ├── Server: not modified → 304 Not Modified (use cache)
            └── Server: changed → 200 + new resource

Nginx expires Directive

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    # HTML: no cache (always fetch fresh)
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-cache, no-store, must-revalidate";
        add_header Pragma "no-cache";
    }

    # Hashed static assets: permanent cache (Webpack hash ensures uniqueness)
    # e.g. main.a1b2c3d4.js, app.5e6f7g8h.css
    location ~* \.(js|css)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Images: longer cache
    location ~* \.(png|jpg|jpeg|gif|webp|svg|ico)$ {
        expires 30d;
        add_header Cache-Control "public";
    }

    # Fonts: permanent cache
    location ~* \.(woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Access-Control-Allow-Origin "*";
    }
}

Fine-Grained Cache-Control

nginx
# Cache-Control header reference:
# public   — can be cached by any middleware/proxy
# private  — only the user's browser can cache (not proxies)
# no-cache — must revalidate with server before using cache
# no-store — must not store any cache
# max-age  — seconds until expiration
# immutable — file content never changes, no need to revalidate

# API responses: no cache (dynamic data)
location /api/ {
    proxy_pass http://backend;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    add_header Vary "Authorization";  # different cache per user
}

Practical Frontend Build Strategy

Combine with Webpack content hash for maximum efficiency:

dist/
├── index.html          → no-cache (re-fetch every time)
├── js/
│   ├── app.a1b2c3.js   → 1-year cache (hash changes when content changes)
│   └── vendor.d4e5f6.js → 1-year cache
└── css/
    └── app.g7h8i9.css   → 1-year cache

The core strategy: HTML never caches; everything else caches by hash.

MIT Licensed