Why I built it
A writer auditing a live page needs four things at once: whether it covers the keywords it was briefed on, whether its links behave, how it is actually performing in search, and whether an AI crawler can read it at all. That was four tabs, two logins and a property picker that had to be re-filtered for every single URL. None of those tasks is hard. The context switch costs more than the task, and a page nobody checks is a page that quietly decays.
The decision that mattered
The headline feature was killed by its own measurement. "Our lazy loading hides content from AI crawlers" had been approved as the flagship of the AI-readiness tab. I measured it before building it: the server HTML carried 129% of the rendered page’s word count, so the crawlers were seeing more than the browser showed, not less. The premise was false. The feature died and a structured-data gap detector replaced it, which targets a defect that is actually present.

Four tabs, two logins, and a filter you re-apply every time
The friction was never the work. It was everything around the work.
Checking a page against its brief, checking its links, checking its search performance and checking whether an answer engine can read it are four separate jobs in four separate places. Search Console in particular makes you pick a property and apply a page filter for every URL, which is a small tax paid dozens of times a day.
The second problem is newer. Teams have years of habit around ranking in a search engine and almost none around being readable by an answer engine. Structured-data gaps, crawler-visible content and robots rules for AI bots are all mechanically checkable, and almost nobody checks them, because the tools that would do it were built for a different era of search.
This is just the initial version (technically, version 2.5, version 1 was all those scattered scripts!)
Scripts, then two extensions, then one
It started as a folder of loose scripts people pasted into the browser console. Those became an audit extension, shared round the team with install instructions in a Slack thread. Separately, a second extension pulled Search Console data for the current page, because the property picker was the single most repeated piece of friction anyone had.
Two extensions solving halves of one problem is worse than either, so they were merged. Deliberately assembled as a new build rather than grafted onto whichever was larger, because grafting would have inherited the weaker half’s structure permanently.
The old extensions were left exactly where they were, unmoved and unrenamed. Chrome loads an unpacked extension from an exact path, so relocating them would have broken the tools the team used daily, in the middle of building their replacement, for no benefit.
Every check exists somewhere. None of them is where the page is.
Nothing here is a gap in capability. It is a gap in place: each tool answers its own question somewhere other than the tab the writer is already looking at.
Search Console. The authoritative source for how a page is actually performing, and the reason for most of the friction. Every URL means picking a property and re-applying a page filter, a small tax paid dozens of times a day.
Crawler suites. Thorough, and built to sweep a whole site on a schedule. That is the opposite shape to a writer with one page open who wants an answer in four seconds.
General SEO extensions. Right place, right speed, and no view of performance or of whether an answer engine can read the page. They were designed for an era of search that did not have to worry about the second thing.
The loose scripts. Where this actually started: a folder of snippets pasted into the console. Fast for whoever wrote them, unshippable to a team, and impossible to keep consistent across people.
SEO Deck. One popup, scoped to the tab already open, with the Search Console property matched automatically. The AI-readiness tab is the part nothing else was doing at all.
Three tabs, and the fourth thing nobody was checking.
Everything is scoped to the page in the tab, so nothing has to be pasted, picked or re-filtered.
Audit the page in front of you
Topic gaps against a saved keyword cluster, focus-keyword placement, and a full snapshot: title, meta, heading tree with skipped levels flagged, canonical, robots directives, social tags, hreflang, structured-data types, word count, missing alt text. Plus anchor text, external links and image checks.
- Topic gaps against a saved keyword cluster
- Heading tree, with skipped levels flagged
- Canonical, robots, social tags, hreflang, structured-data types
- Anchor text, external links and missing alt text
I measured the flagship feature and it disappeared
The most useful thing this project produced was a number that killed its own headline.
The belief was widely held and sounded right: our pages lazy-load their content, so an AI crawler that does not execute JavaScript sees very little of them. It was approved as the centrepiece of the AI-readiness work.
Measuring it took an afternoon. The raw server HTML held 129% of the word count of the fully rendered page, because the rendered version collapses content into accordions that the raw markup simply contains. The crawlers were seeing more, not less. The premise was not merely overstated, it was backwards.
So the feature was cut and replaced with structured-data gap detection, which targets a defect that measurably is present. The raw-versus-rendered comparison survives as a cheap health check rather than a headline, which is the right size for a thing that confirms a good state.
The measurement stands at a sample of one page, and it is recorded that way. It is enough to disprove a claim made with confidence and not enough to call the good state systemic.
Bugs I found by measuring rather than looking
- A double-click was needed on several tools and had been documented to the team as a quirk. The cause was not the tools: a browser popup is destroyed the instant its tab navigates, so nothing survived to act after the reload it had just triggered. Moving the work to the background worker fixed it. Proved by disabling only that listener and watching the bug return exactly.
- The muted text colour used for every description and hint failed accessibility contrast at 3.5:1 against a 4.5:1 requirement. Measured with computed styles and the real luminance formula, not judged by eye. Recalculated to 5.1:1.
- A list of six badges placed in a table cell squeezed its own label to zero width. A screenshot made it look fine; measuring the element’s bounding box did not.
- Segment counts on the link audit disagreed with the rows each filter revealed, because the label and the filter were computed separately. Now derived from the same function, and asserted by clicking each segment and comparing.
- Two links to saved assistants were fabricated. I had typed plausible URLs instead of reading them from the source. They are now extracted by pattern from the original files and never retyped.
Removing help made it more accessible, not less
Every tool button carried a permanent description line. The feedback was that they ate the popup, which was true: measured, removing them recovered 102 pixels of 446 visible on one tab alone, the difference between everything fitting and needing to scroll.
The obvious fix, hiding them and showing a tooltip on hover, would have removed the descriptions from screen readers too. Instead they were moved into a described-by target and visually clipped rather than hidden, so assistive technology still receives the full text regardless of hover state. Strictly better, not a trade.
The tooltip itself introduced a bug during the build. The first-use hint dismissed on hover, and dismissing it collapsed the banner and shifted every button up a row, so the cursor landed on a different button while the user was still reading the first one. Caught in a real browser, because a synthetic click never reproduces a cursor that stays where it is. Moving the dismiss to click and focus fixed it: a click has already resolved against its pre-shift target, and keyboard focus is not coordinate-based.
Merged from two predecessor extensions, plus three that had been built and never wired to a button.
The measurement that killed the flagship feature. A sample of one page, recorded as such.
Below the 4.5:1 floor before. Measured with computed styles, not judged by eye.
Shipped internally, with a known route to being a product
It is in daily use by the team it was built for. Three bugs are open and written down rather than quietly carried: two SERP tools depend on a search engine’s result markup and will break when it changes, and the image-weight tool can only time images the browser actually fetched, so on a warm cache it reports fewer than are present. It states the count it measured, which makes the figure partial but honest.
One change stands between this and something anyone could install: each user authorising their own Search Console rather than sharing a connection. That is specified. It also makes onboarding harder, and I have not measured whether correctness costs more adoption than it earns.
What I would do differently
- Measure the premise before scoping the feature. The lazy-loading belief survived a long time because it sounded right, and an afternoon of measurement would have saved it being approved as a headline.
- Decide the distribution model before the build rather than after. Sharing one connection made the first version trivial to install and is now the only thing blocking the tool from going anywhere.
- Treat a screenshot as a hypothesis. Three of the bugs above looked fine in a picture and were obvious the moment anything was actually measured.
Managing a content team and building its tools are one job.
The friction I removed is friction I felt, the checks it runs are the checks I was asking people to run by hand, and the feature I killed was one I had believed myself.
One extension, assembled rather than grafted
Two earlier extensions solved halves of one problem. Merging them into a new build meant not inheriting the weaker half’s structure permanently, and the originals were left untouched so nobody lost a working tool mid-build.
Daily, by the team it was built for
Three bugs are open and written down rather than quietly carried. Two depend on a search engine’s result markup and will break when it changes; the third reports a partial count and says so.
One change stands between this and a product
Each user authorising their own Search Console instead of sharing a connection. It is specified, it makes onboarding harder, and I have not measured whether correctness costs more adoption than it earns.
More projects.
Let’s build something that scales.
If you are trying to make content compound rather than accumulate, that is the problem I like most. Email is quickest, and my calendar is open.



