How to Configure Content Hashing in Vite Production Builds

Stale frontend assets delivered through edge networks are a primary cause of deployment rollbacks and user-facing errors. When Vite generates non-deterministic filenames or relies on chunk-based hashing, CDNs cache outdated payloads indefinitely. This guide provides a symptom-to-resolution workflow for enforcing strict content-based fingerprinting, aligning origin headers, and verifying hash integrity before production promotion. Understanding how the Build Tool & Framework Asset Pipeline Integration dictates cross-framework cache consistency is mandatory before modifying default build outputs.

Symptom Identification & Diagnostic Commands

Establish a reproducible failure baseline before modifying configuration. Stale cache poisoning typically manifests as mismatched ETags, 304 Not Modified responses on updated deployments, or JavaScript runtime errors caused by chunk version skew.

  1. Verify Deterministic Output: Execute vite build twice with identical source code. Compare the resulting manifests.
vite build --mode production
find dist -type f \( -name '*.js' -o -name '*.css' \) | sort > build1.txt
vite build --mode production
find dist -type f \( -name '*.js' -o -name '*.css' \) | sort > build2.txt
diff build1.txt build2.txt

A successful configuration yields zero differences. Any variation indicates non-deterministic hashing.

  1. Inspect Network Waterfalls: Open browser DevTools → Network tab. Filter by JS and CSS. Reload the application after a deployment.
  • 200 OK with Cache-Control: immutable confirms a fresh cache hit.
  • 304 Not Modified on updated deployments indicates the CDN is serving a stale fingerprint.
  • 404 Not Found on dynamic imports signals chunk filename misalignment.
  1. Validate Cross-Chunk Consistency: Ensure all dynamic imports reference identical hash versions. Inconsistent hashing across lazy-loaded routes causes module resolution failures at runtime.

Rollup Output Configuration for Deterministic Hashing

Vite delegates asset bundling to Rollup. By default, Rollup uses [hash], which derives from compilation chunk order rather than raw file contents. This breaks cache invalidation when only a single dependency updates. Override build.rollupOptions.output to enforce strict [contenthash] fingerprinting.

import { defineConfig } from 'vite';

export default defineConfig({
 build: {
 rollupOptions: {
 output: {
 entryFileNames: 'assets/js/[name]-[contenthash].js',
 chunkFileNames: 'assets/js/[name]-[contenthash].js',
 assetFileNames: ({ name }) => {
 if (/\.(css)$/.test(name ?? '')) {
 return 'assets/css/[name]-[contenthash][extname]';
 }
 return 'assets/[name]-[contenthash][extname]';
 }
 }
 },
 manifest: true,
 sourcemap: 'hidden'
 }
});

This configuration guarantees that any byte-level change to a file triggers a filename rotation. The manifest: true flag generates dist/.vite/manifest.json, which maps logical entry points to physical hashed paths for CDN routing and server-side rendering hydration.

Asset & Chunk File Naming Patterns

Granular naming templates prevent directory collisions, simplify CDN purge rules, and isolate framework-specific assets. Configure patterns independently for entry points, dynamic chunks, and static media.

Asset Type Pattern Template Purpose
Entry JS assets/js/[name]-[contenthash].js Guarantees deterministic routing for initial payloads
Dynamic Chunks assets/js/[name]-[contenthash].js Aligns lazy-loaded modules with entry hash rotation
CSS Bundles assets/css/[name]-[contenthash].css Isolates style sheets to prevent FOUC during cache transitions
Static Media assets/[name]-[contenthash][extname] Fingerprinting for fonts, images, and SVGs
Source Maps assets/js/[name]-[contenthash].js.map Hidden mapping files for internal debugging only

Reference the Vite Asset Pipeline Configuration for baseline pipeline behavior when tracing asset resolution paths across monorepo workspaces.

CDN Cache-Control Header Alignment

Content-hashed assets are inherently immutable. Origin servers must explicitly communicate this to edge caches. Misaligned headers negate the benefits of deterministic hashing.

Origin Server Strategy:

  • Hashed assets: Cache-Control: public, max-age=31536000, immutable
  • index.html & unhashed entry points: Cache-Control: no-cache, no-store, must-revalidate
  • CDN Edge: Configure bypass rules for index.html to force origin fetch on every deployment.

Nginx Implementation:

location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|woff2?)$ {
 if ($request_uri ~ "-[a-f0-9]{8}\.") {
 add_header Cache-Control "public, max-age=31536000, immutable";
 }
 expires 30d;
 add_header Vary "Accept-Encoding";
}

The regex match -([a-f0-9]{8})\. ensures long-lived caching applies exclusively to fingerprinted files. Unhashed fallbacks receive standard short TTLs, preventing stale delivery during transitional deployments.

Build Verification & Hash Collision Testing

Validate configuration integrity before CI/CD promotion. Automated verification prevents cache poisoning in staging and production environments.

  1. Parse Build Output: Execute vite build --mode production and inspect dist/assets/. Verify all filenames contain an 8+ character hexadecimal string.
  2. Digest Verification: Cross-reference generated filenames with actual file digests.
cd dist/assets/js
for f in *.js; do
echo "$f -> $(sha256sum "$f" | awk '{print $1}')"
done

The [contenthash] segment must match the first 8 characters of the sha256sum output. 3. Simulate Incremental Updates: Modify a single CSS variable or JSON import. Rebuild. Confirm only the affected asset rotates its hash while unrelated chunks remain static. 4. Audit Manifest References: Parse dist/.vite/manifest.json. Ensure file keys match deployed CDN paths exactly. Mismatched references trigger 404s on dynamic route hydration.

Common Pitfalls & Resolutions

Issue Root Cause Resolution
Hash fails to update when only CSS/JSON changes Using [hash] instead of [contenthash] in assetFileNames. Rollup hashes compilation order, not file content. Replace all [hash] placeholders with [contenthash] in build.rollupOptions.output templates.
Dynamic import chunks return 404 post-deployment Missing chunkFileNames configuration generates inconsistently hashed lazy chunks cached indefinitely by CDN. Explicitly define chunkFileNames: 'assets/js/[name]-[contenthash].js' and verify manifest.json references.
Source map files exposed to public CDN cache sourcemap: true emits .map files alongside hashed assets, violating security policies and bloating edge storage. Change to sourcemap: 'hidden' and configure CDN to block or purge .map extensions. Upload maps to internal artifact registry.

Frequently Asked Questions

Does Vite use contenthash or chunkhash by default? Vite defaults to [hash], which behaves similarly to [chunkhash] in Rollup. It ties filenames to compilation chunk boundaries rather than individual file contents. Explicit [contenthash] configuration is required for true deterministic fingerprinting.

How do I verify that my CDN is respecting the new content hashes? Execute curl -sI https://your-cdn-domain.com/assets/js/main-abc123.js | grep -i cache-control. Confirm the immutable directive is present. Cross-check ETag and Last-Modified headers against your build manifest to ensure origin synchronization.

Will changing the hash pattern break existing service worker caches? Yes. Service workers cache exact URLs. Update your service worker precache manifest to match the new [contenthash] output pattern. Trigger a skipWaiting() update cycle and purge stale caches during the next deployment window.