
The short version
This check fires when a page ships a rel="canonical" link whose href is blank, points at a fragment instead of a real URL, or is broken enough that no crawler can resolve it to an address. A canonical you can't parse is a canonical Google throws away, and once it's gone the page is back to being canonicalized by guesswork.
What "malformed or empty" actually covers
A working canonical needs exactly one thing: an absolute, resolvable URL sitting inside the href. This check groups together every way that single requirement gets broken. Empty href="". A relative path that never resolves because there's no base URL. A raw template placeholder that was never populated. A URL with a stray space, a naked %, or an unencoded angle bracket that snaps the attribute in half. A href="#" or href="javascript:void(0)" left over from a copy-pasted component. None of these name a preferred page, so none of them do the job a canonical exists to do.
The tag is present, which is why it's easy to miss in a manual spot-check. The parser sees <link rel="canonical"> and moves on. Only when something tries to read the href as a URL does the whole thing fall apart. That's the trap: a malformed canonical looks handled and isn't.
What it costs you
An empty or unparseable canonical is treated the same as no canonical at all — Google discards it and falls back to picking a canonical itself, usually the URL it happened to crawl first or the one with the strongest internal links. On a page with duplicates, parameters, or a print/AMP variant floating around, that fallback frequently lands on the wrong version. Ranking signals consolidate onto a URL you never chose, and the page you wanted indexed can get filtered out as a duplicate. Worse than a missing tag, honestly, because a missing tag at least doesn't lull you into thinking the problem's solved.
Real failing markup, and the fix
Here are the patterns this check trips on most, straight from the page source:
<!-- empty href: present but names nothing -->
<link rel="canonical" href="" />
<!-- template variable never rendered -->
<link rel="canonical" href="{{ page.canonical_url }}" />
<!-- placeholder anchor, resolves to the current URL + "#" -->
<link rel="canonical" href="#" />
<!-- unencoded space breaks the attribute -->
<link rel="canonical" href="https://example.com/blue widgets/" />
<!-- relative path with no resolvable base -->
<link rel="canonical" href="../product?id=" />Every one of those collapses to the same correct form — a fully qualified, self-referencing absolute URL:
<link rel="canonical" href="https://example.com/blue-widgets/" />Absolute means protocol and host are spelled out. No template braces, no fragments, no spaces, no assuming the browser will fill in the base. If a value could ever render empty, the correct move is to omit the tag entirely rather than emit a hollow one — a page with no canonical gets Google's self-referencing default; a page with an empty canonical gets a discarded signal.
Malformation types at a glance
| What's in the href | How Google reads it | The fix |
|---|---|---|
href="" | No target — discarded, self-canonical fallback | Emit a real absolute URL, or drop the tag |
{{ variable }} | Literal string, not a URL — discarded | Fix the template so the value renders server-side |
href="#" or href="/" alone | Resolves to a URL you didn't intend | Replace with the page's own absolute URL |
| Space / unencoded character | Attribute truncated, canonical malformed | Percent-encode or slugify the path |
../path (relative) | Google resolves it against the base — brittle, often wrong | Use absolute https:// URLs |
How to detect it
- curl the raw source. Run
curl -s -A 'Mozilla/5.0' https://example.com/page/ | grep -i canonicaland eyeball thehref. If you see empty quotes, braces, a hash, or a space inside it, that's your bug — no rendering needed. - Screaming Frog. Crawl, then open the Canonicals tab and filter for Canonical Missing and any URL that looks non-absolute. Frog also surfaces canonicals it couldn't resolve, which is exactly this class of failure.
- View source vs. rendered. If the raw href holds an unrendered template variable but the canonical only appears correctly in the rendered DOM, you've got a client-side population problem, not just a typo.
- GSC URL Inspection. Inspect the live URL and read the User-declared canonical line. If it's empty or shows a garbled value while Google-selected canonical shows something else entirely, Google has already discarded yours and moved on.
How to fix it
- Find the source. It's almost always a CMS field, a plugin default, or a theme template emitting a value that's blank or unresolved.
- Populate it with the page's own absolute URL as the default, so self-canonical is the fallback for every page.
- Guard the template: if the canonical value is empty, render no
<link rel="canonical">at all rather than an empty one. - Slugify or percent-encode any path that can contain spaces or reserved characters before it ever reaches the href.
- Re-crawl the affected URLs and confirm each canonical is a single, absolute, resolvable address. Then re-inspect one in GSC to confirm user-declared now matches Google-selected.
FAQ
Is an empty canonical worse than no canonical at all?
Functionally they resolve to the same outcome — Google ignores the empty one and self-canonicalizes — but the empty tag is more dangerous operationally. It reads as "handled" in a code review and hides a broken template that may be firing across thousands of URLs. A missing tag is at least honest about being missing.
Can I use a relative URL in the canonical href?
Google will resolve a relative canonical against the page's base URL, so it technically works — but it's fragile. A misconfigured <base> tag, a proxy, or a CDN path rewrite can resolve it somewhere you never meant. Absolute URLs remove the ambiguity entirely. Use them.
My canonical shows a {{ }} placeholder in the source. What broke?
The template rendered the raw variable instead of its value — a server-side templating error, a null field, or a build that didn't hydrate the canonical. Fix it at the data layer so the field is populated before the HTML is emitted, and add a guard that suppresses the tag when the value is null.
Does href="#" count as malformed?
Yes. It resolves to the current URL plus a fragment, and Google strips the fragment, so you effectively get a self-canonical by accident — which is fine on a page that should self-canonicalize and quietly wrong on a duplicate that should point elsewhere. Either way it's not a deliberate signal, so replace it with a real absolute URL.
After I fix the href, how fast does Google update?
It updates on the next crawl and re-processing, which can be days to weeks depending on the URL's crawl frequency. Request indexing in URL Inspection to nudge high-priority pages, but don't expect the Google-selected canonical to flip the same afternoon.
Related reading
Ground yourself in the mechanics with the canonical tag glossary entry and the full Canonical Tags reference. If your canonical is fine but conflicts with a second declaration, see canonical mismatch. And if you're getting no canonical output at all, start with the missing canonical tag check.
Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.
About SEO ProCheck
Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.
Work With Me
Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.







