Technical SEO isn't a checklist of tags. It's a contradiction hunt.

Canonicals, sitemaps and schema are signals Google weighs, not instructions it obeys. The work is removing the contradictions between them.

Last updated

Everything here is a signal, not an instruction

Search Console has a line that stops technical teams cold: “Duplicate, Google chose different canonical than user.” Google’s own Search Central forum carries thread after thread with that title. The tag is present. The tag is correct. Google read it and went with something else. Nobody in those threads has done anything wrong, which is exactly why the message is so disorienting.

The mechanism is in Google’s documentation, in vocabulary worth reading literally. A redirect is a strong signal that its target should become canonical. A rel="canonical" annotation is a strong signal for the URL it names. Including a URL in a sitemap is described as a weak signal. These methods, Google adds, “can stack and thus become more effective when combined.”

Signal. Weak. Stack. Not one of those is the language of a command. Every declaration you make about your own site is an input to a decision Google makes on its own, weighed against every other input, including the ones you made without noticing.

Which is where the checklist genre lets its readers down. A forty-point audit asks whether the canonical tag is present. Google asks whether it agrees with your internal links, your sitemap, your redirect targets and your hreflang set. Presence is easy to check and easy to get right; agreement is neither, and agreement is what the weighing actually turns on. Read it that way and technical SEO turns into something duller and more productive: the job of finding where your site argues with itself.

There is one instrument for this and it is free. The URL Inspection tool in Search Console reports two fields next to each other: User-declared canonical and Google-selected canonical. The first is what you asked for, the second is what you got. If you have never scrolled to them, you have been running an argument with Google for years without once checking the score.

So start there. Take the ten URLs that actually earn money, run each through URL Inspection, and write down both fields. Every row where the two disagree means something upstream is contradicting your declaration, and there are only so many places upstream.

Canonicals: four surfaces have to agree, and the tag is only one

Four surfaces on your site claim to know which URL is the real one, and they have to agree: your internal links, the canonical annotation, the sitemap, and wherever your redirects land. Google’s guidance on this is unusually direct. Don’t specify different URLs as canonical for the same page using different canonicalization techniques.

The shapes this takes are mundane, which is why they survive so long. A header and footer built once, years ago, link the trailing-slash version of every URL while the template’s canonical points at the version without it. Or the sitemap still lists http URLs on a site that moved to HTTPS, which Google specifically warns against. Trickier: a canonical naming a URL that 301s somewhere else, so you have nominated A as canonical and A has replied that it’s actually B. Worst of the four because it looks deliberate, a canonical naming a page you noindexed, which drops that page out of Search instead of consolidating it.

The rules underneath are short: every indexable page carries a canonical naming itself, as an absolute URL, declared one way. If your site is bilingual, hreflang is a fifth voice in the argument, and Google asks that the canonical be in the same language as the page. Getting those annotations right is a job with its own set of rules, and this is the seam where the two meet.

Arabic URLs deserve a specific warning, because the usual regional advice points at the wrong question. It offers a choice: transliterate the slug into Latin characters, or keep the Arabic. That choice barely matters. Google’s URL guidance says characters in the non-ASCII range should be percent encoded, and shows the percent-encoded Arabic form as the recommended one. What matters is that a single Arabic slug exists in several forms that look identical and are not: the readable Arabic your CMS shows you, the percent-encoded form it may or may not emit, and a third variant if the source string arrived carrying harakat or a tatweel, which are separate codepoints and survive into the encoding. A build that emits one form in its links and another in its canonical has invented a disagreement between two of the four surfaces, about a URL that only ever had one spelling. Normalise the slug where it’s generated, then emit that form everywhere.

The fix is a spreadsheet, not a plugin. Crawl the site and export four columns for every indexable URL: the URL as linked, its declared canonical, its sitemap entry, its final redirect destination. Read across each row. The rows that don’t match are the work.

Redirects outrank your canonical tag, and a migration can do it for you

Google lists the ways you can declare a canonical in order of how strongly each one influences the outcome, and a redirect sits above the rel="canonical" annotation. That has a consequence the checklist genre skips. A chain left by a migration three years ago is currently overruling the tag your developer wrote last week, and no amount of correcting the tag will win that argument.

Chains are also a latency bill. Past some length the crawler stops following, and Search Console’s page indexing report names “a redirect chain that was too long” as one cause of a redirect error. Google prints the number: its crawlers follow up to ten hops by default, and specific products’ crawlers may have different limits. Treat the ceiling as irrelevant either way and keep chains under three, preferring one. Each hop is a full round trip before the browser has received a byte of the page, which is why a chain sitting in front of a template shows up as a server-side problem you’ll otherwise spend weeks misdiagnosing.

Three specifics worth checking by hand. Use 301 where the move is permanent: a 301 is read as a signal that the redirect target should be canonical and a 302 is not, so a temporary redirect that was never temporary leaves the choice to Google instead of making it. Query strings are the second: whether one survives depends on how the rule was written, and in both nginx and Apache the default is that it survives. It changes when the rule is written to drop it, or when the target carries a query string of its own, and there nginx and Apache disagree about which one wins. Last, every URL has four spellings by default (www and non-www, http and https), and they should collapse to the single destination your canonical names. A redirect is a safety net for inbound traffic, not the route your own navigation takes.

robots.txt and canonicals want opposite things

Here is a trade-off the checklists present as a settled answer, and it isn’t one. Google’s faceted-navigation guidance recommends disallowing filtered URLs in robots.txt where you don’t need them indexed, and says outright that putting rel="canonical" on them is generally less effective in the long term. Google’s canonicalization guidance says: don’t use the robots.txt file for canonicalization purposes.

Both are correct, because they answer different questions. A disallowed URL is never fetched, so its canonical annotation is never read, so whatever signals it accumulated are never consolidated anywhere. Disallow buys crawl efficiency and pays for it in consolidation. That makes it a real decision, and one to take per class of URL: either these URLs leave the crawl and you accept that links pointing at them are stranded, or they stay crawlable and carry a canonical that folds them into the page you care about.

Two things to get straight while you’re in there. noindex is the wrong tool for saving crawl effort: Google still requests the page and only then discovers the directive, so you have spent the crawl and gained nothing. Nor does robots.txt control indexing — a disallowed page can still be indexed on the strength of links from other sites, and when it is, it appears in results without a description.

Faceted catalogues are where the decision gets made in earnest, and a bilingual one makes it twice. Where a GCC storefront runs both language trees, the filter combinatorics run underneath each of them, so the choice above comes up once in English and once in Arabic. Make it differently in each and the two trees stop competing with everyone else and start competing with each other. Choose once, apply to both. And in either tree, a filter combination with no products should return a 404 at its own URL and not a redirect somewhere friendlier, which is what Google asks for and what stops empty combinations accumulating as indexable pages.

Schema: ship markup for results that still exist

Start with the item that has outlived its result. FAQ rich results stopped appearing in Google Search on 7 May 2026, and Google removed the feature’s documentation the following month. HowTo went the same way earlier, limited to desktop in August 2023 and gone from desktop too by that September. FAQPage markup still validates, and that is the trap: a validator has no opinion about whether the result exists, so the item gets ticked and the result it was shipped for never appears. A 2026 audit checklist still names FAQPage a priority schema type.

What earns its keep is narrower and duller. Organization markup, so the entity behind the site is unambiguous. BreadcrumbList, which still renders on desktop everywhere Search runs. Product markup where you hold real inventory, real prices and real reviews. Article on things that are genuinely articles. The test for each is whether a system you actually maintain can keep the values true, because schema is a claim about the page and claims go stale on their own.

Which is Google’s rule, stated more plainly than most of its documentation: don’t mark up content that is not visible to readers of the page. Placeholder aggregate ratings on a product nobody has reviewed, a marked-up FAQ whose answers were edited out of the page months ago, leftover markup from a template two redesigns old: all of it fails the same test. What bad schema costs you is the sprint you spent building it.

So inventory it. List what each template emits, delete the types whose results no longer exist, and keep only what you can keep honest. On every other surface here the disagreement is between two of your own declarations; this is the one where it’s between your markup and what Google still displays. For a storefront the surviving list is short and mostly lives on the product page, which is the same page where the money is actually being lost.

The index reads the rendered HTML, not your source

The widest gap of all sits between the document you wrote and the document being indexed, and it’s wide because you think you’ve seen both. Google processes a page in three separate phases: it crawls, then it renders in a headless Chromium, then it indexes. The middle phase sits in a queue, which Google says the page “may stay on for a few seconds, but it can take longer than that.” The sentence that matters most is the flattest one in the whole document: if the content isn’t visible in the rendered HTML, Google won’t be able to index it.

Two failures follow from that, and both look fine in a browser. The first is a soft 404: a client-side router handles a missing product by swapping in a “not found” view while the server cheerfully returns 200, so Google sees a successful response to a page with no content and starts classifying real URLs as errors. The second is routing on fragments, which Googlebot can’t reliably resolve — the state after the # is a thing only the browser knows about.

The check takes two minutes and settles it. In URL Inspection, open “View crawled page” for the HTML Google actually holds, and “Test live URL” for what it would fetch right now. Then search that HTML for a sentence you know is on the page. Skip view-source, which shows you the document before the JavaScript ran, and skip DevTools’ element inspector, which shows it after your own browser ran it. Neither is the thing being indexed.

The parts that aren’t levers, and two sitemap details that are

One item on the standard checklist has no contradiction to find, and saying so is more useful than another reminder to check it. The number and order of your headings is worth getting right for semantics and for screen readers, and that is the whole case for them. Google has said that heading order matters to screen readers and not to Search, and that its systems don’t have a problem with multiple H1s.

Two sitemap details do earn attention, and they’re the two that get skipped. A single sitemap file is capped at 50MB uncompressed or 50,000 URLs, and past either limit you split the file and add an index. A large catalogue crosses that line without anyone noticing, and the URLs past it simply aren’t submitted. The other is lastmod, which Google uses only where it is, in its words, consistently and verifiably accurate against the page’s actual last modification. A CMS stamping today’s date across every URL nightly hasn’t signalled freshness; it has made the field worthless and told Google to stop reading it. Populate it from real content changes or leave it out. While you’re in there, put the Sitemap: line in robots.txt so the file is discoverable without a Search Console submission.

Our Site Audit will crawl your links, canonicals, sitemap and redirect targets and show you where the four disagree, which is the slow part of this done for you. What it can’t do is choose between crawl efficiency and consolidation, because that depends on which of your URLs you want earning links. And if the real question is why you’re invisible on a map instead of in the index, that’s a separate ranking system with its own rules, and none of the above will move it.

Standing offer

The hard part is deciding what to do first

Bring us a URL, an audit report, or a proposal you're not sure about. We'll tell you which problems deserve budget this quarter, and you'll leave with an order of operations you can hand to whoever does the work, whether that's us or not.

Book twenty minutes