← back to Interiordesignershowroom
lib/assetv.js
37 lines
// Asset cache-buster. Computes a short version hash from the CONTENT of the css/js
// files at boot; v('/css/site.css') -> '/css/site.css?v=<hash>'. When a css/js file's
// BYTES change and the server restarts (a deploy re-execs this module), the hash
// changes -> the URL changes -> browsers fetch fresh. This is what lets those assets be
// cached HARD (immutable 1y) without the post-deploy stale-CSS risk from cycle 8.
//
// CONTENT hash, not mtime: rsync/git-checkout/clone can set arbitrary mtimes on files
// (a changed file can land with an OLDER mtime, or an unchanged file a NEWER one), so an
// mtime hash can silently fail to bust (serve stale immutably for a year) or bust
// needlessly. A content hash changes iff the bytes change — deploy method irrelevant.
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
function compute() {
try {
const hash = crypto.createHash('sha1');
for (const d of ['css', 'js']) {
const dir = path.join(__dirname, '..', 'public', d);
for (const fn of fs.readdirSync(dir).sort()) {
hash.update(fn).update(fs.readFileSync(path.join(dir, fn)));
}
}
return hash.digest('hex').slice(0, 10);
} catch (e) {
// Loud, not silent: a swallowed failure here would pin every asset to ?v=dev while
// immutable-1y stays active — i.e. serve stale for a year with no trace.
console.error('[assetv] content-hash failed, falling back to ?v=dev:', e.message);
return 'dev';
}
}
const ASSET_V = compute();
const v = (url) => `${url}?v=${ASSET_V}`;
module.exports = { ASSET_V, v };