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.
- Verify Deterministic Output: Execute
vite buildtwice 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.
- Inspect Network Waterfalls: Open browser DevTools → Network tab. Filter by
JSandCSS. Reload the application after a deployment.
200 OKwithCache-Control: immutableconfirms a fresh cache hit.304 Not Modifiedon updated deployments indicates the CDN is serving a stale fingerprint.404 Not Foundon dynamic imports signals chunk filename misalignment.
- 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.htmlto 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.
- Parse Build Output: Execute
vite build --mode productionand inspectdist/assets/. Verify all filenames contain an 8+ character hexadecimal string. - 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.