---
title: "Cache-Busting With No Build Pipeline: Date-Stamped Asset Filenames"
canonical: https://dxdev.com/blog/date-stamped-asset-filenames-cache-bust-on-legacy/
datePublished: 2026-02-19
---
# Cache-Busting With No Build Pipeline: Date-Stamped Asset Filenames

The first time I saw the filename I assumed it was a typo. A tournament page pointed its script tag at `TournamentTools260219.js`. No `.min`, no hash, just six digits glued onto the end of the name. The stylesheet next to it did the same thing: `TournamentTools260219.css`. The commit that introduced them also deleted the references to the prior `TournamentTools250805.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 Classic ASP and IIS with no front-end toolchain, no bundler, and no asset manifest. The more I sat with it, the more I think it is the correct call for this app.

## The problem everybody solves, restated

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. The moment you ship a fix, that is exactly the thing that screws 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 whole genre of solutions is about producing 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 the dirty secret is that some proxies and intermediary caches ignore the query string entirely when deciding whether a resource is fresh. `?v=` is a hint, not a guarantee.
- **Cache-Control gymnastics.** Short max-age, ETags, revalidation. Real, but it trades the free win away. Now you pay for a conditional request on assets that almost never change.

Every one of these needs something the legacy app does not have: a build step that runs, hashes, rewrites references, and emits a manifest. Adopting a bundler to get content hashes into a decade-old ASP app is not a small change. It 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 actually buys

Here is the useful property the convention relies on: a new filename is a new URL. When the HTML points to a path a browser or intermediary has not previously cached, it normally has to fetch the new asset. That is a simpler cache key change than relying on a query-string convention or a revalidation policy, and it is visible in the page source.

Date-stamping the filename gets you that for the price of a rename. The unminified source lives at a stable path under `scripts/src/`. "Compiling" means minify the source and write it out under the dated name. The ASP page that consumed it just points its script and style tags at the new name:

```asp
AddScriptTag("", "/assets/scripts/TournamentTools260219.js")
AddStyleTag("/assets/style/TournamentTools260219.css")
```

replacing 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 literally names the file it wants, and the file it wants is the one I built today. Browser and CDN both see a URL they have never cached, so they fetch it. Stale copies of `...250805.js` keep right on being served from cache to nobody, because nothing references them anymore.

You get most of what content hashing gives you, and you get it with `mv`.

## Where it leaks

I want to be honest about the cost, because the pragmatic-hack genre is full of people selling the upside and pretending the downside is rounding error.

**It is manual.** A bundler renames the file for you 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 are real, and the convention only protects you if you follow it on every single compile.

**The date is informational, 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 does not have this failure mode, because the hash is of the bytes, not the calendar. A small app rarely ships the same asset twice in one day, but "in practice, rarely" is a weaker guarantee than "by construction, never," and you should know which one you bought.

**Orphaned files accumulate.** Every compile leaves the previous dated file sitting in the directory, referenced by nothing. `...250805.js` is dead weight the moment `...260219.js` ships, but nothing deletes it. Over 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 it 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. Your git history now carries the build artifact, and code review of that line is not happening.

## Why it is still the right call here

Put the trade against the alternative honestly. The alternative is: introduce a JavaScript build pipeline into a Classic-ASP revenue app so that an automated step can do the rename a human is currently doing. 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 exists to buy you exactly 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 retire two narrow edge cases.

The date-stamp convention has a property the bundler does not: you can read it. The filename tells you, at a glance, 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. A future me, or anyone else touching this code, sees `TournamentTools260219.js` and knows precisely what they are looking at. Boring and auditable beats clever and opaque when the thing you are protecting is a revenue app you maintain alone and have to reason about cold six months from now.

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. That is the same instinct showing up twice in one change: solve the actual problem in front of you with the smallest mechanism that holds, and skip the framework that would solve a more general problem you do not have.

## The takeaway

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. Date-stamping is a pragmatic substitute for content hashing that leaves two visible limitations: same-day collisions and orphan cleanup.

I would not reach for this on a greenfield app with a bundler already in the loop. Use content hashes when that pipeline already exists. On a legacy stack with no toolchain, the calculus can flip. A small naming convention may be the safer immediate change if the team can follow it reliably and understands where it stops being sufficient.

## Related

- [Server-Side JavaScript on Classic ASP in 2026: Prototype Pages and the DB Helper Pattern](server-side-javascript-on-asp-prototype-pages-dbobj): the development context this asset convention lives inside
- [clipboard.js Named Its Global 'Clipboard'. So Did the Browser. Boom.](clipboardjs-global-collision-with-browser-api): another case where a small, defensive change was safer than a broad dependency upgrade
