Browser caching decides whether a returning visitor re-downloads your files. The safe pattern is to cache fingerprinted assets for a year, because their URLs change with their content, and to make the HTML page that references them revalidate every time.
Build tools such as Vite and webpack put a content hash in file names (app.3f9a1c.js). A given URL then never changes, so the browser may keep it for a year and, with the immutable directive, skip revalidation even on reload. Choosing the value with a map keeps it working in front of a proxied app too: a regex location with no handler of its own would serve files from the root directory and shadow the proxy.
map $uri $cache_control {
default "";
~*\.(?:js|mjs|css|woff2|woff)$ "public, max-age=31536000, immutable";
}
# in the server block
add_header Cache-Control $cache_control always;The HTML file is what names the current asset URLs. If it is cached for long, visitors keep loading the old bundle after a deploy. no-cache does not mean do not store: it means store, but check with the server before each use, which a 304 answers cheaply.
map $uri $cache_control {
default "";
~*\.html$ "no-cache";
}A location block that defines any add_header discards every add_header inherited from the server block. Adding Cache-Control in a location can therefore silently remove your HSTS and nosniff headers for those files. Repeat them in the location, or include a shared snippet.
Open the Caching & compression generator
no-cache lets the browser store the response but forces revalidation before reuse. no-store forbids storing it at all, which you only need for sensitive responses.
Only if their file names change when their content does. Otherwise a replaced image stays stale in visitors' caches for the whole max-age.
No. JPEG, PNG, WebP and WOFF2 are already compressed, so gzip adds CPU cost for almost no size saving. Compress text formats: HTML, CSS, JavaScript, JSON, SVG and XML.