Vite’s caching strategy, particularly with content hashes and Cache-Control headers, isn’t just about speeding up your browser; it’s about ensuring that when your code does change, users get the new code, not stale, broken builds.

Let’s see Vite in action. Imagine a simple React app.

# Initial setup
npm create vite@latest my-vite-app --template react
cd my-vite-app
npm install
npm run build

After npm run build, you’ll find a dist/ directory. Inside, you’ll see files like index.html, assets/index-a1b2c3d4.js, and assets/index-e5f6g7h8.css. Notice those alphanumeric strings? Those are content hashes.

When you serve this dist/ directory with a web server, the crucial part is how that server is configured to send Cache-Control headers. For example, with Nginx, you might have:

location /assets/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

location / {
    try_files $uri $uri/ /index.html;
    add_header Cache-Control "public, max-age=0";
}

Here’s the breakdown:

  • Content Hashes (a1b2c3d4): Vite generates these hashes based on the content of each file. If a JavaScript file changes even by one character, its hash will change. This is the core of its cache-busting mechanism.
  • Cache-Control: public, immutable for Assets: This header tells the browser (and any intermediate caches like CDNs) that these hashed assets can be cached aggressively and never change. The immutable directive is key; it signals that the resource’s content will not change. Since the filename itself changes when the content does, the browser can safely cache index-a1b2c3d4.js forever. When Vite produces index-f9g8h7i6.js in the next build, it’s a completely new URL, and the browser will fetch it.
  • Cache-Control: public, max-age=0 for index.html: The index.html file doesn’t have a hash. It’s the entry point. It needs to be served fresh every time so it can point to the latest hashed assets. max-age=0 tells the browser to revalidate with the server on every request, ensuring you get the most up-to-date index.html that references the correct, newly hashed JavaScript and CSS files.

The problem Vite solves is the "stale-while-revalidate" nightmare of traditional SPAs where a new JS bundle is deployed, but users, still holding onto an old index.html in their browser cache, are pointed to a non-existent or incompatible old asset. Vite’s strategy decouples the entry point from the hashed assets, and the Cache-Control headers ensure each part is handled appropriately.

The mental model is: your application is divided into two types of resources. The first are static, immutable assets (your JS, CSS, images, etc.) that can be cached indefinitely because their filenames guarantee their content. The second is the dynamic entry point (index.html) that acts as a manifest, always pointing to the correct, latest versions of those static assets.

When you deploy a new version of your application, the index.html file is updated to reference the new content hashes for your JavaScript and CSS. Your web server, configured with aggressive caching for assets and revalidation for index.html, ensures users get the latest index.html which then reliably loads the correct, new hashed assets.

One thing most people don’t realize is the power of the immutable directive in Cache-Control. While max-age can tell a browser how long to consider a resource fresh, immutable goes further by stating the resource will never change. This allows caches to skip revalidation entirely, leading to even faster load times for repeat visits, as the browser knows for certain it has the correct, latest version of that specific asset URL without even asking the server.

The next hurdle is understanding how to integrate this with your CDN’s caching policies for maximum global performance.

Want structured learning?

Take the full Vite course →