Shipping & Infra5 min read

160 Pages Were Serving Unstyled. Only My Browser Looked Fine.

A full-repo mirror upload overwrote the shared stylesheet with a four-month-old file. The file was there — it was just 2.5KB. I recovered the lost original from Chrome's disk cache.

#deployment#gotchas#reality-check#debugging#static-sites
Left panel: a 2,581-byte stylesheet with v1 class names matching none of the live v3 markup. Right panel: a 24KB stylesheet served under a new filename to bypass the edge cache.
The file was there. It was a 2,581-byte file from four months ago.

The report was short: "that page opens but there's no content."

I opened it. It looked fine to me. Normally you'd suspect a cache or a regional issue on their side — but here the suspicion pointed the other way. The one seeing something wrong was correct, and my browser was the outlier.

A question. Where, other than your own browser, do you check what your site actually serves right now?

The content was all there

Fetching the HTML showed the whole page: headings, paragraphs, links, all present. What was missing was the styling.

I opened assets/style.css. 2,581 bytes. A four-month-old version. Its class names were header.top, .card, .grid — first-generation markup.

The 160 pages live at that moment were third-generation markup: site-header, product-hero, doc-summary-item. So not one rule matched. App detail pages, legal pages, support pages, and the app directory were all being served unstyled.

The culprit was the deploy script. lftp mirror --reverse inside upload.sh overwrites the live site with the whole repo — and that repo was a generation behind the live site. "Mirror" sounds like synchronization; it is one-way overwrite.

The browser cache hides the whole incident

This is why it went unseen for days. My browser still held the v3 CSS from an earlier request, so the same URL rendered correctly for me. The server was already serving the broken file — I was just the one person not receiving it.

Two ways to see what the origin actually serves:

  • Request it with a query string. style.css?cachebust=1 changes the cache key and forces a fresh fetch.
  • curl your own domain from the server. But this may still show you the edge — after I fixed the origin to 24KB I kept getting 2,581 back, which is how I learned Cloudflare was holding it.

One rule came out of this. Confirming with ls -la that "the file exists" means nothing. The entire incident was 2.5KB versus 24KB. Look at size and content, not existence.

What would you do here?

The origin was clobbered, the repo that clobbered it had no current CSS, the generator lives on another machine, and backups didn't have it either. Two options:

  1. Rewrite the v3 CSS from scratch.
  2. Find a surviving copy somewhere.

Option 1 means restoring every class 160 pages actually use, and the result would differ subtly. I tried option 2. And a copy existed — inside my own browser. The very cache that had been hiding the incident.

Recovering an asset from Chrome's disk cache

~/Library/Caches/Google/Chrome/<Profile>/Cache/Cache_Data/

Scan those cache files for the gzip magic bytes \x1f\x8b, then decompress from that offset with zlib.decompressobj(16 + zlib.MAX_WBITS), and the original text comes out.

The question is whether to trust it, and there was a basis: two distinct cache entries decompressed to byte-identical output. Two requests at different times had received the same file.

Validation was by content, not size:

  • Do the braces balance? (is the file truncated?)
  • Does this CSS define every class the live pages use?

Fixing the origin doesn't fix it while the edge holds a copy

I restored the original and uploaded it — and visitors still got the old CSS. Cloudflare was caching it, and we have no purge available.

So I deployed under a filename that had never been cached: style-v3.css, with the pages' links repointed. A new URL has no edge entry, so it takes effect immediately. I also updated the original style.css with the same content, so that path self-heals once the cache expires. Same wall as Why my site kept showing the old version; this time the way around it was the filename.

A site with mixed generations needs split stylesheets

While recovering, I counted the actual distribution. Of 442 pages, 160 are v3, and 163 actually link the shared CSS (the rest use inline styles). Of those 163, only 3 were v1 markup.

Because of those 3, merging into one stylesheet is guaranteed to collide on selectors both generations use, like .wrap. So I split them into style-legacy.css. The decision procedure is simple: diff the set of classes a page uses against the set each CSS defines.

Three things to check

  • Does your deploy script contain a full overwrite like mirror --reverse or rsync --delete? What guarantees the source is newer than live?
  • Have you ever fetched your shared assets bypassing cache (?cachebust=1)? Was the size what you expected?
  • Do the live pages use any class that the shared CSS doesn't define?

The honest part

For prevention I added a confirmation gate (OOTSSU_UPLOAD_CONFIRM=yes) to upload.sh, plus a warning that live may be newer and only changed files should go up. But that's a defense that only works if a human reads it.

The real lesson is elsewhere. A full mirror is a deploy you can't easily undo, and a tool that treats a stale copy as the source of truth is quietly destructive. This script never failed. It successfully published a four-month-old file every time.

The part that still bothers me is that a cache is why I didn't see it for days. The cache was both the reason the damage stayed invisible and the reason the lost CSS was recoverable at all.

Do one thing right now: fetch your site's shared CSS with ?cachebust=1 appended and check the byte count. Is it the number you expected?

YouTube

160 Pages Were Serving Unstyled. Only My Browser Looked Fine.

Related