CrawlCheck

Findings · 2026-09-08 · By · 0 views

Our scanner said the viewport tag was missing. It was there all along.

A page scored 0 out of 100 on the mobile section with a valid viewport meta in its head. The attribute was unquoted, and four of our extractors only matched quoted attributes.

On 8 September our own scanner told one of our properties that its viewport meta tag was missing. It scored the mobile section 0 out of 100, and reported no canonical and a meta description of zero characters.

All three tags were in the head. Here they are, copied out of the delivered bytes:

<meta content="width=device-width,initial-scale=1.0" name=viewport>
<link href=https://emmanuelorta.com/ rel=canonical>

Unquoted attributes are valid HTML, and minifiers produce them

The attribute values carry no quotes. That is valid HTML5: a value with no spaces, quotes, equals signs or angle brackets does not need them, and every browser parses it correctly. Minifiers strip those quotes because they are free bytes on every page load.

On that page there were 64 unquoted rel= attributes and 11 unquoted name=, and not one quoted rel=. Four of our extractors matched on name="viewport" with the quotes required. They found nothing — and nothing is indistinguishable from absent.

The correlation across six measured sites was exact

Same scanner, same day, same checks. The only variable was whether a minifier had been near the markup.

SiteUnquoted rel=Unquoted name=Mobile section
emmanuelorta.com64110 / 100
crawlcheck.io00100 / 100
credentialrecord.com01100 / 100
treeservicedenverllc.com01100 / 100
terrariumstation.com00100 / 100
supremefencinglakewood.com00100 / 100

One property served minified HTML with unquoted attributes. It scored zero. The five serving quoted attributes scored one hundred. That is not a distribution — it is a switch, and the switch was in our code.

The obvious fix was also wrong

The intuitive repair is a global regular expression: find bare attribute values, wrap them in quotes, move on. Run that over the tag above and it matches width=device-width inside the already-quoted content value and injects a quote into the middle of it. The scanner then read the viewport as the string width=.

The code compiled. The syntax gate passed. The output was garbage. It was caught only because the patch was run against the real page before it shipped, not because any test asserted it. A check that proves your code parses tells you nothing about whether it is right.

The working version is a small tokeniser that walks a tag attribute by attribute and skips over anything already inside quotes. It rewrites name=viewport and leaves class="x y" and text content untouched, byte for byte. We verified that on all six sites above before deploying: the five that already worked read identically afterwards.

A second defect was hiding underneath the first

Fixing the quoting exposed it. Two of those extractors read the meta description with a pattern that required name= to appear before content= in the tag. HTML attributes have no required order. A page emitting content= first reported a zero-length description with a 302-character description sitting right there. Both now try either order.

After both fixes, on the same page: viewport read, canonical read, theme colour and touch icon read, description 302 characters. The mobile section went 0 to 100, schema 56 to 67, the machine-file trust chain 86 to 89.

Why our own self-audit never noticed

The scanner runs a set of invariants that compare its own fields against each other, on the principle that every wrong answer it has ever produced was a contradiction inside its own output. That set never fired here, and it could not have.

Both halves of every comparison were blind in the same direction. The extractor said no viewport; every field derived from it agreed there was no viewport. Two measurements that share a defect agree perfectly. Internal consistency is a real test, but it cannot see a failure that is upstream of everything it compares.

So we added a ninth invariant. If a title was read out of the head, the page is over 20,000 bytes, and the record is not refused, blocked or parked — and that same head yields neither a viewport nor a canonical — the likelier explanation is our parser, not a site that declares neither. Severity 3, it cannot move anyone's grade, and it quarantines rather than repairs.

We calibrated it against 19 stored scan records before shipping. It fires on the three records from the broken page and stays silent on the other sixteen, across eleven domains. A detector that fires on everything is one nobody reads — the same lesson as the 403 rate we nearly published before grouping it by IP.

Check your own site in ten seconds

View source on your homepage. Find the viewport tag. Look at whether the attribute name is quoted — name="viewport" or name=viewport. If it is unquoted and an audit tool reports the tag as missing, the tag is not the problem.

This generalises past us. Any tool that reads HTML attributes with a regular expression rather than a parser inherits two assumptions it never states: that values are quoted, and that attributes appear in a fixed order. Neither is required by the specification. Both are broken by ordinary minification, which means the sites most likely to be misread are the ones that optimised their delivery — the opposite of what the score is supposed to reward. If you are comparing tools, what each one actually fetches and how it parses it matters more than the length of its checklist.

Live, as you read this: the corpus now holds 2,198 domains across 1,635 scans. The figures in this piece were measured on the date above; this line is not.

Every figure above came out of this scanner.

Point it at your own domain and see the same measurements, free.

Scan a domain — free

Related findings

Writing about this? Facts, live figures and marks — every number on that page is dated and traceable to a scan.

All findings · The dataset · How the dataset works