Rules · seo/*
SEO rules
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.
$ void seo60 rules32 error · 22 warn · 6 infoupdated 16 Sept 2026
- seo/http-statuserrorPage does not return HTTP 200Google only indexes URLs served with 200; 4xx/5xx pages are dropped and AI crawlers log them as wasted fetches. This is a scoring gate.
- seo/redirect-chainwarnURL reaches its content through more than one redirectEach hop costs crawl budget and latency; Google follows up to 10 hops but URL Inspection follows none, and AI crawlers spend ~14% of fetches on redirects.
- seo/redirect-temporarywarnTemporary redirect (302/303/307) usedTemporary redirects tell search engines to keep the old URL indexed; for moved content the signals don't consolidate on the new URL.
- seo/client-redirectwarnRedirect done with meta refresh or JavaScriptCrawlers that don't run JavaScript stay on the original URL, and Google treats meta refresh/JS redirects as weaker signals than HTTP redirects.
- seo/noindexerrorIndexable page carries noindexA noindex in <meta name=robots>, <meta name=googlebot> or X-Robots-Tag removes the page from Google, and from AI Overviews/AI Mode and every answer engine built on a search index. This is a scoring gate.
- seo/robots-snippet-restrictedinfonosnippet or a very low max-snippet limits previewsGoogle says nosnippet and max-snippet also stop the page being used as direct input for AI Overviews and AI Mode.
- seo/soft-404errorMissing page returns HTTP 200 (soft 404)Unknown URLs that answer 200 get crawled and indexed as thin duplicates, and AI crawlers already waste about a third of fetches on 404s. In Next.js 16, notFound() inside a Suspense boundary streams a 200 with noindex.
- seo/html-too-largeerrorUncompressed HTML is over Googlebot's 2 MB fetch limitGooglebot reads only the first 2 MB of an HTML file (uncompressed); content, links and JSON-LD past the cut-off don't exist for indexing. Next.js inlines the RSC payload, roughly doubling text weight.
- seo/rsc-payload-ratioinfoInline RSC flight payload dominates the HTMLself.__next_f.push scripts duplicate server-rendered text for hydration; when they exceed half of a large document they push content towards the 2 MB crawl cap and slow TTFB.
- seo/not-httpswarnPage served over plain HTTPSecure serving is a page experience signal and browsers mark HTTP pages as not secure.
- seo/origin-redirectserrorhttp:// or www/apex variant doesn't redirect to the canonical origin in one hopVariants that serve 200 create duplicate sites, and multi-hop redirects (http → https → www) waste crawl budget.
- seo/html-langerror<html lang> missing or not a valid BCP 47 tagScreen readers, Bing, translation and agents use lang to process the text; an invalid tag is ignored.
- seo/viewporterrorViewport meta missing, not device-width, or blocks zoomGoogle indexes the mobile rendering; without width=device-width the page renders at desktop width on phones. Blocking zoom fails accessibility.
- seo/charsetwarnCharacter encoding not declared earlyBrowsers and crawlers need the charset within the first 1024 bytes (or in Content-Type) to decode text correctly.
- seo/title-missingerrorNo single non-empty <title> in <head>The title is the main input for the search result title link and for how agents label the page. Titles outside <head> (Next.js streamed metadata) are unreliable for crawlers.
- seo/title-lengthwarnTitle length outside 10–60 characters, or keyword-stuffedGoogle has no hard limit but truncates titles to the device width; very short titles are rewritten, and repeated keywords look like stuffing. This is a heuristic.
- seo/meta-description-missingerrorNo meta description in <head>Without one, search engines and link unfurlers pick arbitrary page text for the snippet; agents also use it as the page summary.
- seo/meta-description-lengthwarnMeta description length outside 50–160 charactersGoogle truncates long descriptions and may ignore very short ones in favour of page text. This is a heuristic, not a Google limit.
- seo/metadata-in-bodyerrorCrawler receives <title>/canonical/description inside <body> (Next.js streaming metadata)On request-time routes Next.js 16 streams metadata into <body> for any user agent not in htmlLimitedBots, and the default list excludes Googlebot and all AI crawlers. Google only accepts rel=canonical in <head> (it stays in <body> even after rendering), and non-rendering AI crawlers may miss the title.
- seo/canonical-missingerrorNo single rel=canonical in <head>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.
- seo/canonical-invaliderrorCanonical URL is relative or contains a fragmentGoogle asks for absolute canonical URLs; relative ones get resolved against unexpected bases (previews, proxies) and fragments are ignored. This is a scoring gate.
- seo/canonical-brokenerrorCanonical points to a URL that returns 4xx/5xxA canonical to a broken URL tells Google to index nothing. This is a scoring gate.
- seo/canonical-conflicterrorCanonical target redirects, is noindex, or canonicalises elsewhereContradictory signals (canonical → redirect, canonical → noindex page, canonical chains, noindex + cross canonical) make Google ignore the canonical and choose its own.
- seo/canonical-not-selfinfoCanonical points to a different URLThat's correct for duplicates and syndicated copies, but a template bug (every page canonicalising to the home page, or to page 1 of a series) de-indexes the site.
- seo/head-js-mutationerrorJavaScript changes canonical, robots, title or description after loadGoogle may skip rendering when raw HTML says noindex, and it compares raw vs rendered canonicals; non-rendering AI crawlers only ever see the raw values. Changed canonical/robots is an error, changed title/description a warning.
- seo/h1-counterrorPage doesn't have exactly one visible <h1> in the server HTMLGoogle uses the first visible h1 as a title-link source and Bing expects the h1 to reflect the title; agents and Markdown converters use it as the document title.
- seo/h1-title-mismatchinfo<h1> and <title> share little wordingMicrosoft recommends the h1 closely reflect the title; large mismatches make Google rewrite the title link.
- seo/heading-skipwarnHeading levels skip (h2 → h4) or headings are emptyThe heading outline is how crawlers, screen readers and AI section parsers split the page; skips and empty headings break the structure.
- seo/img-alt-missingerror<img> without an alt attributeAlt text is how Google Images, screen readers and text-only AI crawlers understand images. A missing alt attribute (not an empty one) is an accessibility failure.
- seo/img-alt-qualitywarnAlt text is a filename, a generic word, or overly longAlt such as "image", "IMG_2231.jpg" or a pasted paragraph adds no meaning for search or assistive tech.
- seo/links-not-crawlableerrorNavigation uses elements crawlers can't followGoogle and AI crawlers only follow <a href> with a resolvable URL; javascript: hrefs, onclick handlers and clickable divs hide pages from discovery.
- seo/link-textwarnLinks with generic or empty textAnchor text is a relevance signal for the target page and the only context screen-reader and agent users get; "click here" and icon-only links carry none.
- seo/broken-linkswarnInternal links return 4xx/5xx (or go through redirects)Broken links waste crawl budget and dead-end users; ChatGPT and Claude crawlers spend about 34% of fetches on 404s. Broken nav/footer links repeat on every page (error).
- seo/trailing-slasherrorBoth trailing-slash and non-slash URLs serve 200/about and /about/ are different URLs; serving both creates duplicates that split links and crawl budget.
- seo/hreflang-invaliderrorhreflang uses invalid codes or relative URLsGoogle ignores hreflang annotations with invalid language/region codes (en_US, region-only) or non-absolute URLs.
- seo/hreflang-not-reciprocalerrorhreflang set isn't self-referencing and reciprocalIf two pages don't both point to each other, Google ignores the tags; each variant must also list itself.
- seo/og-missingerrorRequired Open Graph tags missing (og:title, og:type, og:image, og:url)Link previews in Slack, LinkedIn, iMessage, X and chat assistants fall back to guesses or no card at all.
- seo/og-incompletewarnog:description / og:site_name missing, or og:url ≠ canonicalA page-level openGraph object in Next.js replaces the layout's entirely (verified on 16.3.5), silently dropping siteName and locale; og:url that differs from the canonical attributes shares to another URL.
- seo/og-image-invaliderrorog:image isn't a reachable, absolute, raster image within size limitsScrapers need an absolute URL returning 200 image/* (not SVG), ≤ 8 MB for Facebook and ≤ 5 MB for X, or the card renders without an image.
- seo/og-image-sizewarnog:image is smaller than 1200×630 or far from 1.91:1Below 600×315 platforms show a small thumbnail instead of a large card; 1200×630 renders crisply everywhere.
- seo/og-image-metawarnog:image:width/height missing or wrong, or og:image:alt missingFacebook renders the first share immediately only when width and height are declared, and they must match the real image.
- seo/twitter-cardwarntwitter:card missingX shows a small summary card without it; title, description and image can fall back to Open Graph, but the card type can't.
- seo/faviconerrorNo crawlable favicon in a Google-supported formatGoogle shows the favicon next to search results and needs a crawlable, square (≥ 48×48) ICO/PNG/GIF/JPEG/BMP. SVG-only favicons aren't in Google's supported list.
- seo/apple-touch-iconwarnapple-touch-icon missing or not a 180×180 PNGiOS home-screen bookmarks and some favicon consumers (including Google) use it.
- seo/manifestinfoWeb app manifest missing or incompleteThe manifest supplies install name, icons and colours for browsers and app surfaces.
- seo/theme-colorinfoNo theme-color metaMobile browsers tint UI with theme-color; light and dark variants avoid a mismatched bar.
- seo/robots-txt-unreachableerrorrobots.txt returns 5xx/429 or times outWhen robots.txt errors, Google stops crawling the site (for up to 12 hours), then falls back to a cached copy for up to 30 days.
- seo/robots-txt-missingwarnNo robots.txtA 404 means "crawl everything", but you lose the Sitemap directive and any explicit AI crawler policy.
- seo/robots-txt-invalidwarnrobots.txt serves HTML, exceeds 500 KiB, or has unparseable linesA catch-all route returning HTML for /robots.txt means no rules; content after 500 KiB is ignored; malformed lines are skipped silently.
- seo/robots-blocks-allerrorrobots.txt disallows the site for * / Googlebot / BingbotDisallow: / blocks crawling entirely, which removes the site from Google, from Bing (and so Copilot and ChatGPT's search partner), and from AI answers. Usually a preview config leaking to production. This is a scoring gate.
- seo/robots-blocks-resourceserrorrobots.txt blocks JS/CSS needed to renderGoogle renders pages with their scripts and styles; blocking /_next/ makes it see a broken page.
- seo/robots-sitemap-directivewarnrobots.txt has no absolute Sitemap: lineThe Sitemap directive is how crawlers other than Google (Bing, AI search crawlers) find your sitemap; it must be an absolute URL.
- seo/sitemap-missingerrorNo reachable sitemapSitemaps are the main discovery channel for new and updated URLs, especially for sites with few external links.
- seo/sitemap-invaliderrorSitemap isn't valid sitemap XML or exceeds protocol limitsMalformed XML, HTML served at /sitemap.xml, or files over 50,000 URLs / 50 MB are rejected.
- seo/sitemap-url-invaliderrorSitemap <loc> is relative or on another hostThe protocol requires fully qualified URLs on the sitemap's own host; others are ignored.
- seo/sitemap-lastmodwarn<lastmod> invalid, in the future, or identical across entriesGoogle uses lastmod only if it is consistently accurate; lastModified: new Date() stamps every URL with the build time, so Google learns to ignore it.
- seo/sitemap-url-statuserrorSitemap lists URLs that don't return 200 directlySitemaps should only contain canonical, live URLs; 404s and redirects waste crawl budget and erode trust in the sitemap. Over 5% of sampled URLs failing is a scoring gate.
- seo/sitemap-url-not-indexableerrorSitemap lists noindex, robots-blocked or non-canonical URLsListing a URL in the sitemap says "index this"; noindex, a robots block or a canonical pointing elsewhere contradict it.
- seo/duplicate-titlewarnMultiple pages share the same <title>Duplicate titles make pages look like duplicates and Google rewrites them; usually a missing generateMetadata.
- seo/duplicate-descriptionwarnMultiple pages share the same meta descriptionA layout-level description inherited by every page gives search engines no page-specific snippet.