A static site can fail without a server error. The HTML returns 200, the stylesheet returns 200, and every file is syntactically valid. The page is still broken because the browser received files from different releases: new markup with an old stylesheet, or an old document that points at an asset already removed.

This is not merely a cache problem. It is a release-integrity problem. HTML and the assets it names form one compatible set, even when a browser, an edge cache, and an origin store them separately. The release process has to preserve that relationship during normal delivery, failover, and rollback.

Name the compatibility contract

The URL in a document is a promise: fetching that URL will return an asset compatible with this document. Reusing the same URL for different bytes weakens the promise. A browser is allowed to keep the older response, a content delivery network may refresh at a different time, and two origins may disagree briefly after a deployment.

Document
The HTML that defines structure and references assets
Asset identity
A URL that changes when the asset bytes change
Cache policy
How each layer decides whether a stored response is still usable
Publication unit
The complete set of files made visible as one release
Verification
Evidence that every public entry point serves the intended set
Rollback unit
The same complete set, restored without mixing generations

A successful upload is therefore weaker evidence than a successful release. The release is complete only when the document points at immutable or correctly revalidated assets, each public route serves the expected bytes, and the previous compatible set remains recoverable.

A response can be fresh according to its cache headers and still be wrong for the document that requested it.

Give changing assets an identity

When an asset changes, change the URL. A content hash in the filename gives the strongest identity: a name such as site.55718b376e93.css describes one exact byte sequence and can safely live for a long time. A version query such as site.css?v=55718b376e93 is a smaller retrofit and works when every cache in the delivery path includes the query string in its key.

The value should come from the delivered bytes, not from a date typed by hand. Content-derived keys remove a decision from the release: unchanged content keeps its URL; changed content cannot accidentally reuse it. If the deployment system cannot generate hashed filenames, computing one short digest for the query key is still better than a generic version number that someone may forget to advance.

  • Change identity with content. New bytes receive a new asset URL.
  • Reference the new identity from HTML. Updating the asset without updating its document leaves the release incomplete.
  • Keep old identities available. Previously cached HTML may request them until that document expires.
  • Verify cache-key behavior. Confirm that the edge and origin do not discard or normalize the version component.
  • Avoid purge as the primary design. Purges are useful recovery tools, but correctness should not depend on every cache forgetting at once.

Cache according to what may change

Documents and fingerprinted assets have different jobs. HTML is the pointer to the current release, so it should be cheap to revalidate. A fingerprinted asset is historical content with a permanent identity, so it can be cached aggressively. Giving both the same policy either slows the site unnecessarily or makes updates unreliable.

HTML
Revalidate on use, or keep a deliberately short lifetime; do not mark mutable documents immutable
Fingerprint assets
Use a long lifetime and immutable semantics because a content change creates a new URL
Stable-name assets
Revalidate unless a versioned query is proven to be part of every cache key
Redirects
Cache cautiously while routes are changing; an old permanent redirect can outlive the release that created it
Error pages
Keep negative caching bounded so a transient missing file does not remain missing after repair

Inspect the headers that actually reach a public client. Configuration intent is not proof: a hosting layer can add, replace, or ignore directives. Record the response status, cache policy, validator, age, and asset digest from outside the origin. That observation becomes the baseline for the next release.

Publish one release, not a sequence of files

Uploading files in arbitrary order creates a period when the public directory contains two generations. Publishing HTML first can make browsers request assets that do not exist yet. Publishing assets first is safer only if their new identities do not replace files used by the old HTML. Replacing stable-name assets first can break clients that still hold the previous document.

The clean model is to build a complete release in a new location, validate it there, and switch one pointer when it is ready. An object-backed host may provide this behavior as a deployment primitive. On a conventional server, a versioned directory and an atomic current-directory switch provide the same boundary.

  1. Build a closed set. Generate HTML, assets, headers, redirects, and the not-found page together.
  2. Validate before visibility. Resolve every local reference, check expected metadata, and reject missing files.
  3. Publish immutable assets. Make every newly referenced identity retrievable before changing the current document.
  4. Switch the release once. Use the platform’s deployment boundary or one atomic pointer change.
  5. Retain the previous set. Keep it until the verification window and the oldest supported document cache have passed.

If the platform offers no atomic switch, make the order explicit and design for overlap: upload new immutable assets, publish the new HTML, verify, and remove old assets only after their callers can no longer be in circulation. The overlap costs storage, but it avoids a correctness dependency on timing.

Check every public entry point

A fallback origin is useful only when it serves the same public contract. Comparing home-page screenshots is not enough. Both entry points should agree on the status and bytes of each release resource, including the stylesheet, icons, article pages, headers where the platforms allow parity, and the not-found behavior.

Inventory
The exact public paths expected in the release
Status map
The response status for every path through each entry point
Byte digest
A hash of each successful response body, compared across origins
Negative route
A unique missing path checked independently for the intended 404 response
Release marker
A small recorded identifier tying the verification to one deployment

Do the comparison through the public routing layers, not by reading the files on disk. A proxy rule, cache, rewrite, or stale replica can change the response after the origin file has been verified. This is the same outside-in principle used in a public-service preflight: test the contract where a reader receives it.

Verify what a browser can retain

A fresh command-line request proves what a new client receives. It does not prove what a returning browser sees. Keep one browser check in the release: load the previous site, deploy, navigate normally without clearing storage, and confirm that the new document uses its compatible assets. Then repeat in a private session to cover the cold path.

  • Warm path: an existing browser cache reaches the new release without a forced refresh.
  • Cold path: a new session receives the intended document and asset identities.
  • Layout: the page has no obvious unstyled content, overflow, missing icons, or font fallback surprises.
  • Network: every referenced asset succeeds and no obsolete path becomes a hidden dependency.
  • Navigation: the home page, archive, new article, and one missing path behave as recorded.

This short check catches the precise failure that isolated byte comparisons miss: individually valid responses combined in an invalid browser state.

Roll back HTML and assets as a set

A rollback is another release. Restoring only the HTML may point at assets already removed; restoring only a stylesheet may make the current markup incompatible. Switch back to the last verified release directory or redeploy its complete manifest, then repeat the public status, digest, negative-route, and browser checks.

Record the previous release identifier before publication, not after a failure. The recovery instruction should name one known-good set and one switching action. If rollback requires reconstructing which files once belonged together, the release boundary was never strong enough.

The useful unit of change is not a file. It is a compatible, addressable, and recoverable release.

  • The HTML references content-derived asset identities.
  • Mutable documents revalidate; immutable assets keep long-lived URLs.
  • The complete release is validated before one visibility switch.
  • Old asset identities remain available for old documents.
  • Every public entry point agrees on status and response bytes.
  • A warm browser and a cold browser both receive compatible files.
  • The previous complete release can be restored with one documented action.

Static delivery becomes predictable when caching is part of the compatibility model rather than a speed setting added afterward. Give changed bytes a new identity, publish documents and assets as one set, and verify the set through every route that can serve it.