← All projects

Page Historicals

Page Historicals takes a web address and hands back that page every six months, oldest on the left. Full screenshots, in a row you can drag and zoom into. It pulls the old copies out of the Wayback Machine and renders each one in a real browser.

I built it to watch a company change its mind about itself. What a brand claimed to be in 2013, what it claims now, and how the design carried the claim each time. Reading that one snapshot at a time in the archive is slow enough that I kept not doing it.

The strip only works if the gaps are honest. A period the archive doesn't hold and a period where the site sat still look identical once they're pictures in a row. So which is it? Every card has to say.

Stripe's 2018 page came back with no styling

The archive gives me pages. I turn them into pictures myself. Sometimes it kept the HTML and lost the stylesheets. Raw text on white. It looks like a plain site from 2003, and a naive build files it as history.

Stripe's 2018 snapshot applied zero CSS rules and loaded one of its twelve images. Every intact capture in the same survey applied 371 to 5983 rules. The number was already sitting there. I just wasn't reading it.

Those captures get kept and badged. The screenshot is real evidence about the archive's copy. It says very little about how Stripe looked that year.

Then a card came back labelled Jan 2027 (the run was August 2026). Defensible window math. Indefensible caption. The grid stops at the last anchor that's actually happened.

Same snapshot, two widths

I captured one Stripe snapshot at 3177 pixels wide, then captured the same snapshot again at 1440. A wide element had landed late on one run and not the other. A row of cards at random widths isn't a comparison of anything.

Every card is taken at the configured viewport width now. Fixing the screenshotter wasn't enough. The cache compared the options a saved image was captured under against the options I was asking for. The bad image's options were perfectly current. It would have been served forever. The cache reads the pixels too now.

The archive pushes back by hanging up

It doesn't send a 429. It drops the connection. One real run lost 14 of 20 cards to a closed connection and saw no 429 at all. The deeper retry budget I'd built for throttling didn't engage where it mattered. A drop while several of my own captures are in flight counts as backpressure now. A drop when I'm only asking for one thing stays an honest outage.

Four captures at a time (a review agent ran it at twelve and lost most of its cards).

The pages I render are hostile input. I'd stopped treating them that way. An archived page runs its own scripts in my Chromium, on my machine, and for a stretch it could have called my local API and picked where that browser went next and what it screenshotted. Per-run options are a frozen list of presentation knobs now, and anything that changes state has to come from the page I served.

Where it stands

Apple's homepage from 2019 at six-month steps: 11 clean captures, 3 marked degraded, 3 lost to dropped connections. I checked the degraded three by eye and they really are unstyled. Stripe at twelve-month steps from 2012 came back 16 of 16.

All 375 tests pass. A saved strip still renders when the archive is down, read off the disk and labelled as saved. The CLI exits on its own code, and no script can mistake it for a fresh run.

Here's the gap I keep hitting. The strip shows how a page looked. Positioning lives in the words, and the words are the headline, the subhead and the button. At the zoom where a decade fits on screen you can't read any of them, and nothing in the tool extracts a single word of text yet.

That's what I'm building next. Every capture already runs a script inside the page to measure how well it rendered, so the headline can ride along in that same pass.

If you've got a brand whose positioning you've watched move, send me the domain and I'll run it.