This site is a static blog. Perhaps two thousand lines of markup and styling, six essays, one interactive chart. It is close to the simplest thing that can still be called a website.

Building it produced six bugs. Not one of them threw an error, broke a layout, or left a red mark anywhere. Every one rendered a page that looked entirely correct and quietly did nothing.

I want to write them down, because the category is more interesting than any of the individual faults — and because the defences most teams have are pointed somewhere else entirely.

The Six

A canonical tag that pointed at the homepage. The rule I inherited from an earlier project was that a page’s canonical URL must be derived from its own route, never falling back to a default. Fall back to / and every page on the site quietly announces itself as a duplicate of the homepage — which is a documented route into Google’s “Crawled – currently not indexed” state. The page renders identically either way. The tag is invisible. You find out weeks later, in a report nobody opens.

A content security policy that blocked the chart from working. The interactive chart on this site is server-rendered to static SVG at build time and then made interactive in the browser. The framework does that with two small inline scripts. A strict CSP blocks inline scripts unless you allow them explicitly — so the chart rendered, beautifully, and was inert forever. No console error visible in normal use, no missing element, no layout difference. A picture of a chart.

A function passed where only data can travel. Component props on this site are serialised to JSON so they can cross from the build to the browser. A function does not survive that trip; it arrives as null. The component then threw on its first render in the browser and was blanked — after having server-rendered perfectly. The built HTML was correct. The live page was empty.

A stale cache that built seven pages instead of nineteen. Every essay was silently missing. The build reported success. All the automated checks passed — because those checks validated the pages that existed, and said nothing about the pages that should have. It deployed. This is the one that still bothers me.

A “wide” figure that could never be wide. A CSS max-width cannot exceed the parent’s max-width. The setting was there, it was syntactically valid, and it did precisely nothing. Later, after a layout change, the same figures started overflowing the container by 168 pixels on one side — because they were centred on their parent, and the parent had moved.

A lazy-loading directive that never fired. The chart was set to become interactive when scrolled into view. It never did, in any browser, locally or deployed, with no error either time. The server-rendered version sat there looking finished.

What They Have in Common

None of these is exotic. They are ordinary mistakes.

What they share is that the output was correct. A rendered page, valid HTML, a chart with the right shape. If your verification is “look at it,” all six pass. If your verification is “does it throw,” all six pass. If your verification is a screenshot in a pull request, all six pass.

They fail only one test: did the thing actually do what it was for?

That question is harder to ask than it sounds, because the artefacts do not distinguish between working and merely present. A chart that is inert looks like a chart. A canonical tag pointing at the wrong URL looks like a canonical tag. A build that produced a third of its pages looks like a build.

The Fix Is Not More Care

The instinct is to be more careful. I do not think that works, and I say that as someone who was being quite careful and shipped all six anyway.

What worked was converting each one into something that fails loudly — a build error, not a habit:

  • The number of pages built must equal the number of source files that should produce pages. Count them and compare.
  • Every inline script’s hash must be present in the policy that will govern it. Compute both, compare.
  • No component prop may serialise to null. If it did, something that was supposed to be data was not.
  • No URL may appear in the sitemap while also carrying noindex. Submitting a page for indexing while telling crawlers to ignore it is a contradiction.
  • Every canonical URL must match the path of the page emitting it.

Each check took a few minutes. Each replaces a category of bug that is otherwise invisible until it has been live long enough to matter.

The general form: assert the invariant, not the appearance. Do not check that the page looks right — check that the count matches, the hash is present, the reference resolves.

The Part I Got Wrong Twice

Two of the guards above did not work the first time I wrote them.

The policy check searched for the required hostname anywhere in the security policy. It passed while the relevant directive still blocked the script, because the same hostname appeared in a different directive nearby. A substring match on a structured policy is worthless; I had written a check that could not fail.

And the two scripts that compute inline-script hashes — one to allow them, one to verify them — had drifted to slightly different definitions of what an inline script is. A mismatch there means the two disagree about what needs allowing, in either direction, and the result is the exact silent failure the tooling exists to prevent.

The lesson I take is uncomfortable: a guard you have not watched fail is not a guard. It is a line of code you believe in. The only way to know a check works is to break the thing on purpose and confirm the check catches it — which takes about a minute and which I now do for every one.

Why This Is a Measurement Problem

This site is about measurement and its failure modes, and the connection is direct enough to be worth stating.

The recurring theme in the essays here is that we act on what is instrumented rather than on what matters, and that the gap between the two is invisible precisely because it is not instrumented. Attribution funds what it can see. Productivity reporting counts what it can count.

A silent failure is the same shape, one level down. The dashboard is green because the dashboard measures whether the build exited zero, not whether the build produced the site. The page looks right because “looks right” is what we instrumented, and “works” is what we needed.

The fix is the same in both cases, and it is not more diligence. It is choosing a measurement that would actually change if the thing were broken.

Limitations and Honest Caveats

  • This is one small static site. A larger system has failure modes I have not hit here, and some of these checks would be impractical at scale.
  • Five of the six were found by looking, not by tooling. The checks exist so they cannot recur; they did not catch the first instance. I do not have a method for finding the first one beyond suspicion and checking.
  • “Assert the invariant” is easy to say and often hard to specify. Some of these invariants were only obvious after the bug.
  • Survivorship bias applies to this entire piece. These are the silent failures I found. I have no way to know how many are still running.

Next Steps

  • Take one thing you shipped recently and ask what it would look like if it were broken. If the answer is “the same,” you have found a place where a check is worth more than a review.
  • Pick your single most consequential silent-failure candidate and write the assertion for it. Then break it on purpose and confirm the assertion fires.

Questions this piece answers

Why do silent failures matter more than crashes?
A crash reports itself. A silent failure produces output that looks correct, so it passes review, ships, and survives until someone happens to check the specific thing it broke. The cost is not the bug; it is the months between shipping it and noticing.
What is the difference between a canonical tag that is wrong and one that is missing?
A missing canonical is usually harmless — search engines infer one. A canonical pointing at the wrong URL actively tells them the page is a duplicate of something else, which can remove it from the index. Both render identically in a browser.
How do you test for a failure that has no symptom?
You assert the invariant rather than inspecting the output. Instead of checking that pages look right, check that the number of pages built equals the number of source files; instead of checking that a script tag is present, check that the policy actually permits it.

Sources

  1. Astro documentation — Template directives (client:idle, client:visible) and how island props are serialised
  2. MDN — Content-Security-Policy: script-src, including hash-source semantics
  3. Google Search Central — How to specify a canonical URL, and what happens when one points elsewhere
  4. Google Search Central — Sitemaps best practices, on submitting URLs that carry noindex