Fingerprint CSS and JS URLs so deploys never serve stale styles
Assets are cached for a week, so the browser kept the previous styles.css and rendered the new markup with old rules. Each build now appends a content hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
// Builds the static site into ./dist. No dependencies: `node build.mjs`.
|
// Builds the static site into ./dist. No dependencies: `node build.mjs`.
|
||||||
import { mkdir, rm, writeFile, cp } from 'node:fs/promises';
|
import { mkdir, rm, writeFile, cp, readFile } from 'node:fs/promises';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { page, eyebrow, btn } from './src/layout.mjs';
|
import { page, eyebrow, btn } from './src/layout.mjs';
|
||||||
@@ -12,22 +13,27 @@ await rm(out, { recursive: true, force: true });
|
|||||||
await mkdir(out, { recursive: true });
|
await mkdir(out, { recursive: true });
|
||||||
await cp(join(root, 'static'), out, { recursive: true });
|
await cp(join(root, 'static'), out, { recursive: true });
|
||||||
|
|
||||||
|
// Fingerprint CSS/JS so a new deploy never reuses a stale cached copy.
|
||||||
|
const hashOf = async (f) => createHash('sha256').update(await readFile(join(root, 'static', f))).digest('hex').slice(0, 10);
|
||||||
|
const assetVersions = { '/assets/styles.css': await hashOf('assets/styles.css'), '/assets/site.js': await hashOf('assets/site.js') };
|
||||||
|
const fingerprint = (html) => Object.entries(assetVersions).reduce((h, [u, v]) => h.replaceAll(`"${u}"`, `"${u}?v=${v}"`), html);
|
||||||
|
|
||||||
const urls = [];
|
const urls = [];
|
||||||
for (const name of pages) {
|
for (const name of pages) {
|
||||||
const def = (await import(`./src/pages/${name}.mjs`)).default;
|
const def = (await import(`./src/pages/${name}.mjs`)).default;
|
||||||
const file = join(out, def.path, 'index.html');
|
const file = join(out, def.path, 'index.html');
|
||||||
await mkdir(dirname(file), { recursive: true });
|
await mkdir(dirname(file), { recursive: true });
|
||||||
await writeFile(file, page(def));
|
await writeFile(file, fingerprint(page(def)));
|
||||||
urls.push(def.path);
|
urls.push(def.path);
|
||||||
console.log('built', def.path);
|
console.log('built', def.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFile(join(out, '404.html'), page({
|
await writeFile(join(out, '404.html'), fingerprint(page({
|
||||||
path: '/404/',
|
path: '/404/',
|
||||||
title: 'Page Not Found | Dylan Mazzei',
|
title: 'Page Not Found | Dylan Mazzei',
|
||||||
description: 'This page could not be found.',
|
description: 'This page could not be found.',
|
||||||
body: `<section class="page-hero"><div class="wrap-narrow stack center" style="align-items:center;text-align:center">${eyebrow('404')}<h1 class="h-display-lg">Page Not Found</h1><div class="btn-row">${btn('BACK TO HOME', '/')}</div></div></section>`,
|
body: `<section class="page-hero"><div class="wrap-narrow stack center" style="align-items:center;text-align:center">${eyebrow('404')}<h1 class="h-display-lg">Page Not Found</h1><div class="btn-row">${btn('BACK TO HOME', '/')}</div></div></section>`,
|
||||||
}));
|
})));
|
||||||
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
await writeFile(join(out, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>
|
await writeFile(join(out, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|||||||
Reference in New Issue
Block a user