Thousands of pages in five languages. SEO does not break loudly — it breaks silently, and you find out in the traffic three weeks later. So the site now checks itself.
Every page exists in several languages. One mistake in a shared control — a canonical built from the wrong host, a missing return link between translations — is not one broken page. It is thousands.
A canonical pointing somewhere else tells Google to ignore the page. Nothing visibly breaks.
A language link with no matching link back is invalid, so the alternates are thrown away.
See the site the way a search engine sees it, on a schedule, and keep every crawl to compare against.
Monitoring only helps if the thing it finds can be fixed in one place. That was the first job.
Built by one helper, never from the incoming request host — so a dev or preview host can never publish itself as canonical.
One control emits the alternates for every page, so the set is consistent and reciprocal by construction.
Product and listing markup generated from the database, so what Google reads is what the catalogue actually says.
Only real language codes are accepted as a URL prefix, and the English prefix redirects once, permanently.
A real example of why this matters: an unconstrained language segment meant any first path element looked like a language. Pages such as /france and /french-polynesia were being mangled by it. The fix was a database-driven list of real languages — one change, every route corrected.
Each run is kept whole, so “when did this start?” is answerable instead of guessable.
A crawl on its own is a long list. Two crawls are a change report, which is what anyone can act on.
A fix is not finished when it is deployed. It is finished when the next crawl stops reporting it.
Indexability and the reason if not — canonicalised, noindex, redirected.
The HTTP status actually returned, not the one we expect.
Title, meta description and first heading, as served.
The canonical it declares, so a self-demoting page is obvious.
Locale derived from the URL, so problems can be counted per market.
Every language alternate, with the page it points at.
If the English page says “the French version is here”, the French page must say “the English version is here”. Search engines discard the whole set when it does not.
Because every alternate is stored as its own row, the check is simply: is there a row with the two ends swapped? A missing one is a broken return tag, found automatically.
Technical health is half of it. The other half is whether a rewrite earned more clicks — which needs the page content and its performance captured at the same moment.
The page's content is captured and hashed at a point in time, so “the version that was live then” is a real object.
Clicks, impressions, click-through rate and average position for that page over a date window, from Search Console.
Observed search positions for a keyword, by device and country, recorded as they are seen.
Two snapshots of the same page, each with its own numbers — a before and after that is evidence, not a feeling.
Why snapshot the content too: without it, a traffic change six weeks later cannot be tied to anything. With it, you know exactly which words were live when the numbers moved.
A background job fills the content tables for each language from the English source, so a new page exists everywhere rather than only where someone got round to it.
Titles and descriptions are translated as their own job — the fields that decide whether anyone clicks.
The assistant queries the crawl directly: which pages lost indexability since last week, which hreflang pairs are one-sided, which titles are missing.
Canonical ownership, language routing with a permanent redirect, hreflang alternates, product and listing structured data, translation jobs for every language.
The scheduled crawl and its history, SERP position tracking, and Search Console performance attached to content snapshots. The first runs are in.
Scheduled crawl of the live site, loaded into our database.
Clicks, impressions, CTR and position per page.
Content and metadata across every supported language.
Lets the assistant query crawls and snapshots directly.
● Never guess the canonical — built from configuration, not the request
● One redirect, not a chain — the English prefix resolves in a single hop
● Crawls are immutable — history is kept, never overwritten
● A stalled crawl is visible — a run still marked running is itself the alert
● Translations never block a release — they fill in behind, they do not gate the English page