A page pointed its script tag at ScheduleTools260219.js, and we assumed it was a typo. No .min, no hash, just six digits glued onto the end of the name. The stylesheet next to it did the same thing: ScheduleTools260219.css. The commit that introduced them also deleted the references to the prior ScheduleTools250805.js and ...250805.css.
It is not a typo. 260219 is 2026-02-19. 250805 is 2025-08-05. The compiled asset is named after the day it was compiled, <Name><YYMMDD>.js. That is the entire cache-busting strategy for an app that runs on a legacy web stack and IIS with no front-end toolchain, no bundler, and no asset manifest.
The problem everybody solves
Cache-busting exists because the win you want and the bug you get are the same mechanism. You want assets served from a CDN edge or the browser disk cache forever, on every repeat visit. Then you ship a fix, and that same behavior works against you. The user is holding a cached copy of your JavaScript. You pushed a corrected one. Same URL. Every cache layer between your origin and their tab is doing its job by handing them the old bytes.
The usual solutions all produce a new URL when, and only when, the content changes:
- Content hashing.
app.4f3a9b.js. The build computes a hash of the file contents, a manifest maps the logical name to the hashed name, and the HTML references the hashed name. New content, new hash, new URL. This is what webpack, Vite, and friends do. - Query strings.
app.js?v=37. Cheap, but some proxies and intermediary caches ignore the query string entirely when deciding whether a resource is fresh.?v=is a hint and carries no guarantee. - Cache-Control gymnastics. Short max-age, ETags, revalidation. Real, but it gives the free win away. Now you pay for a conditional request on assets that almost never change.
Each of these needs something this app does not have: a build step that runs, hashes, rewrites references, and emits a manifest. Adopting a bundler to get content hashes into an older web application is a new pipeline, a new failure surface, and a new thing that has to be installed and green on whatever box does the building.
What the date stamp buys
The convention leans on one property: a new filename is a new URL. When the HTML points to a path a browser or intermediary has never cached, it has to fetch the new asset. That is a simpler cache key change than a query-string convention or a revalidation policy, and you can see it in the page source.
Date-stamping the filename costs a rename. The unminified source lives at a stable path under scripts/src/. “Compiling” means minifying the source and writing it out under the dated name. The page that consumes it points its script and style tags at the new name:
AddScriptTag("", "/assets/scripts/ScheduleTools260219.js")AddStyleTag("/assets/style/ScheduleTools260219.css")Those lines replaced the ...250805 versions that were there before. That is the whole deploy story for the asset. No manifest to keep in sync, nothing to read at request time to resolve a logical name to a physical file. The HTML names the file it wants, and the file it wants is the one we built today. Browser and CDN both see a URL they have never cached, so they fetch it. Stale copies of ...250805.js keep sitting in cache with nobody asking for them, because nothing references them anymore.
You get most of what content hashing gives you, and you get it with mv.
Where it leaks
The cost deserves a plain accounting.
It is manual. A bundler renames the file as a side effect of building. Here, a human renames it, and a human swaps the reference in the page. Forget the swap and you ship new bytes that nothing points at, or you point the page at a file you forgot to commit. Both can happen, and the convention only protects you if it is followed on every compile.
The date is informational and not unique. 260219 tells you when, not what. Two compiles on the same day collide: the second writes over the first at the same filename, and now you have new content at a URL the cache may already be holding. A content hash cannot do this, because the hash is computed over the bytes. This app rarely ships the same asset twice in one day, but that is a weaker guarantee than one that holds by construction, and you should know which one you bought.
Orphaned files accumulate. Every compile leaves the previous dated file in the directory, referenced by nothing. ...250805.js is dead weight the moment ...260219.js ships, but nothing deletes it. Over the years the asset directory fills with a fossil record of every build. Harmless, but it is litter, and there is no garbage collector because there is no build system to run one. You clean up by hand on a separate pass.
The minified blob is committed. Source and compiled output both live in the repo, so the diff for a “compile” is a one-line minified monster. The committed bundle starts var NEW_LINE='\n';function Set_CopyButton_Success(){... and runs the whole file on a single line with no trailing newline. Git history now carries the build artifact, and nobody is code-reviewing that line.
Why it is still the right call here
The alternative is to introduce a JavaScript build pipeline into a legacy revenue app so that an automated step can do the rename a human is doing now. That pipeline is a dependency to install, a config to maintain, a step that can break the deploy when it breaks, and a manifest layer the request path now has to consult. All of that machinery buys two things the date stamp lacks: same-day uniqueness and automatic cleanup. For an app that ships an asset a handful of times a year, that is a lot of new opaque infrastructure to cover two narrow edge cases.
The date stamp also has a property the bundler lacks, which is that you can read it. The filename tells you, in the page source, in the repo, in the CDN logs, which version of the asset is live and when it was cut. There is no manifest to cross-reference and no hash to decode. Anyone touching this code later sees ScheduleTools260219.js and knows exactly what they are looking at. When the thing you are protecting is a revenue app you have to reason about cold six months from now, boring and auditable wins over clever and opaque.
The same compile that shipped this file added a guarded loader so the page only pulls the clipboard library once, deduped on a this.tags[src] flag. It is the same instinct showing up twice in one change: pick the smallest mechanism that holds, and skip the framework that solves a more general problem you do not have.
We would not reach for this on a greenfield app with a bundler already in the loop. Use content hashes when that pipeline exists. On a legacy stack with no toolchain the calculus can flip, and a small naming convention may be the safer immediate change if the team can follow it reliably and understands where it stops being sufficient.
What the filename is really doing
Cache-busting does not require a build system. It requires the release process to point clients at a changed asset URL when the content changes, and the filename is a URL you already control. If your web application has no build pipeline, find out who swaps the reference at each release. Date-stamping has two known limits: two releases on the same day collide, and old files pile up until someone removes them by hand.