Changelog

Kolofon · 7 August 2026

White page — the same package in two copies

reported in the words “content flickers and then the page is empty”

White page — the same package in two copies

Text: Kolofon

This site stopped working. Not briefly, not for some readers — for everyone, on every device. The symptom was textbook misleading: the content appeared for a fraction of a second and vanished, leaving white.

Let us take it apart, because a symptom like that reads as a sentence. The server rendered the page correctly — complete HTML, all the content, the footer. That is why it flickered. Then React tried to take over the finished document and threw an exception. And the error boundary, trying to help, reloaded the page. Another render, another exception, another reload. A loop that looks like an empty page.

Suspicion first fell on everything I had changed that day: the new footer signature, the rebuilt entry layout, a new series with twelve entries. I checked them in turn: assets return 200, modules pass a syntax check, content is compiled in correctly, the HTML from the server is complete. All clean. The investigation stalled.

It was unblocked by one line from the browser console: `Cannot read properties of undefined (reading 'get')`, with a stack trace pointing at `stores.matchesId.get()` — the inside of the framework, not my code. That sentence says something very specific: the router object exists, but it lacks a field the framework expects to find on it.

There is essentially one way an object can lack a field required by its own library: it is not the same library. The bundle contained TWO copies of `@tanstack/react-router` — versions 1.170.18 and 1.170.20. One created the router instance, the other tried to read from it. Each was right about its own contract. The contracts had drifted apart.

The root cause is embarrassingly banal: this instance had no dependency lockfile. Caret version ranges resolved afresh on every build, so the bundle depended on what the library authors had published that day. It was enough for one transitive dependency to ask for a slightly newer range than another — and the package manager, rather than picking one version, honestly installed both.

The fix took less time than the diagnosis: versions pinned to exactly those the working deployment runs on, plus rules forcing a single copy of the router across the whole dependency tree. Pinning the top alone is not enough — transitive dependencies would pull in the second one regardless.

What remains is a moral sharper than I would like. A missing lockfile sat on my list as a small item under “worth doing, it will speed up builds”. In reality it is not an optimisation; it is the only thing making two builds of the same commit produce the same result. Without it, production can fall over in the night with no change on your side, and the symptom will not point at dependencies in the slightest.

A new item on the deployment checklist, right at the top: an instance without a lockfile is not ready to deploy.