Immutable asset / content hashing
Baking a hash of a file's contents into its filename, so it can be cached forever and any change ships as a brand new URL.
See it
What it is
The build tool hashes the contents of each asset and bakes that hash into the filename: 'app.js' ships as 'app.a1b2c3d4.js'. Because the name is derived from the bytes, a given URL can only ever mean one exact file. That lets you serve it with Cache-Control: public, max-age=31536000, immutable and never think about purging it again. Change one character of source and the build emits a different name, so browsers fetch it as a genuinely new URL.
Reach for it on everything with a build step behind it: JS chunks, CSS, fonts, sprites, compiled images. Vite, webpack, Next.js, and Astro all do this by default. The rule of thumb: hashed assets get the forever cache, and the HTML that references them gets a short TTL (or no cache at all), because the HTML is the one file that has to change in place to point at the new hashes.
Gotcha: 'immutable' really means immutable. If you overwrite a hashed file on your origin with different bytes, every browser and edge node that already cached it will happily serve the stale copy for a year, and a purge will not reach the browser cache at all. Second trap: hashing everything invalidates more than you meant to if one chunk's hash depends on its neighbors, so a one-line change can rename half your bundles. Stable chunk splitting and deterministic module IDs fix that.
Ask AI for it
Configure the build so every static asset (JS, CSS, fonts, images) is emitted with a content hash in its filename, e.g. app.[contenthash].js. Serve those hashed files with 'Cache-Control: public, max-age=31536000, immutable', and serve the HTML entry document with 'Cache-Control: no-cache' so it always revalidates and picks up the new hashes. Use deterministic module IDs and stable chunk splitting so an unrelated edit does not rename every chunk. Never overwrite a hashed file in place.