Why it matters
Without a canonical in <head>, Google picks one itself from duplicates (trailing slash, query strings, www), splitting signals. Canonicals in <body> or conflicting canonicals are ignored. This is a scoring gate.
How to fix it
Emit exactly one absolute, self-referential canonical in <head> on every indexable page via alternates.canonical (absolutised by metadataBase).
Example
// lib/seo.ts — one helper so every page emits the full set (nested metadata objects are replaced, not merged)
export function pageMetadata({ path, title, description }: { path: string; title: string; description: string }): Metadata {
return {
title,
description,
alternates: { canonical: path }, // '/blog/hello-world' → https://acme.example/blog/hello-world
openGraph: { url: path, title, description, siteName: site.name, locale: site.locale, type: 'website' },
}
}References
More seo rules
void seo reports 60 rules in this category. What search engines need from the raw response: status codes, titles and descriptions in <head>, self-referencing canonicals, one h1, crawlable links, Open Graph, icons, robots.txt and a sitemap with real dates.
title-lengthTitle length outside 10–60 characters, or keyword-stuffedmeta-description-missingNo meta description in <head>meta-description-lengthMeta description length outside 50–160 charactersmetadata-in-bodyCrawler receives <title>/canonical/description inside <body> (Next.js streaming metadata)canonical-invalidCanonical URL is relative or contains a fragmentcanonical-brokenCanonical points to a URL that returns 4xx/5xxcanonical-conflictCanonical target redirects, is noindex, or canonicalises elsewherecanonical-not-selfCanonical points to a different URL