| name | seoauditor |
| description | Audit how the codebase makes a website visible to search engines and AI answer engines end to end (crawlability and indexation, rendering and content-in-HTML, on-page content and semantics, canonicalization, structured data, AI/generative-engine visibility, URL architecture and technical config, Core Web Vitals code signals, social metadata, internationalization, feeds and syndication, and SEO observability), write seoaudit.md (a scored, prioritized, self-contained report), then display the results in chat. Read-only: never crawls the live site, never runs Lighthouse, never calls Search Console or a model. Invoke with /seoauditor in Claude Code or $seoauditor in Codex. |
Invocation: type /seoauditor (Claude Code) or $seoauditor (Codex) to run this command. The same skill works in both tools. Treat any text after it as the optional path-or-scope argument (a subpath, a single template or route group, or one locale).
seoauditor
Audit how the codebase in the current working directory makes its website visible to search engines and to AI answer engines end to end: how it controls crawling and indexation, how it renders content into HTML that crawlers and AI fetchers actually receive, how on-page content and semantics are structured, how URLs are canonicalized and deduplicated, how structured data is modeled, how it is discoverable and citable by generative engines, how URLs and technical configuration are set up, how performance and Core Web Vitals are signaled in code, how link previews are built, how it handles multiple languages, how it syndicates content, and how SEO is measured and protected from regression. Then do two things:
- Write a report named
seoaudit.md at the root of that codebase.
- Display the results in this chat: the overall score, the scorecard, and the top fixes (see "Report in chat" below). Do not finish silently.
This is read-only analysis of the code as written. Do not crawl or fetch the live site, do not run the application, do not run Lighthouse or PageSpeed Insights, do not call Google Search Console, Bing Webmaster Tools, the Rich Results Test, or any model or AI engine, and do not mutate anything. Work from the templates and components, the framework metadata APIs, the robots.txt / llms.txt / ai.txt / sitemap / feed / manifest generators, the head and structured-data markup, the routing and rendering config, the redirect and header config, and the build and CI configuration in the repository. The only file you create is seoaudit.md. If an optional path or scope was provided as an argument, audit that subtree, template group, or locale; otherwise audit the whole visibility surface rooted at the working directory.
The report is written for a reader who has no memory of the audit, typically an AI agent that will open seoaudit.md later and decide, on its own, what to fix. Every finding must therefore stand alone: cite exact locations (path/to/template_or_config.ext:line), name the tag, attribute, route, config key, or generated file, carry its own context, and say how to verify the fix. If a finding cannot be acted on by someone who only has the report and the code, it is not finished.
This is a search and AI visibility, indexability, and discoverability audit. Scope is everything that determines whether content can be crawled, rendered, indexed, ranked, and cited: the crawl and index controls, the rendering path, the on-page and structured signals, the URL and infrastructure config, and the measurement around it. General code quality belongs to codeauditor, the user experience and journeys to uxauditor, the full application security surface to secauditor, the database layer to dbauditor, and the LLM-integration internals to llmauditor; touch those only where they create a visibility-layer defect. Where performance overlaps uxauditor and where crawl-affecting security headers overlap secauditor, this audit covers the visibility-specific slice (does the signal reach the crawler, does it get the page indexed and cited) and says so.
Operating principles (non-negotiable)
These govern the whole audit. A report that violates any of them is not done.
- Evidence over assertion. No claim without a concrete, checkable reference (
file:line) and the signal path that makes it real: the tag and its value, the config key and its branch, the generator and what it emits, the route and its rendering mode. Apply the substitution test to every sentence: if you could swap in a different project and it would read equally true, it is filler. "SEO could be improved" fails. "app/layout.tsx:14 sets robots: { index: false } with no environment gate, so every route ships <meta name=\"robots\" content=\"noindex\"> to production and the whole site is deindexed" passes.
- Verify against what reaches the crawler, not the intent. Read the rendered output path and the emitted markup, not a comment, a plugin name, or the README. A
react-helmet <title> in a client-only SPA reaches no non-JS crawler; a metadata.alternates.canonical with no metadataBase resolves to localhost in production; a <meta name=\"robots\" content=\"noindex\"> on a URL that is Disallow-ed in robots.txt is never seen. The gap between the markup's apparent intent and what is actually served and crawlable is itself a finding, and often the most serious one.
- Refuse theater. Hunt for paper controls. The most dangerous defects look like SEO but carry no weight: a
noindex: line in robots.txt (never standardized, ignored since 2019); <priority>/<changefreq> filled on every sitemap entry (Google ignores both); rel=next/rel=prev pagination tags (unused by Google since 2019); a build-time lastmod stamped uniformly on every URL (distrusted); a hardcoded homepage canonical on every page (collapses the whole site to one URL); a self-referencing canonical with no enforcing redirect so the www/http/slash duplicates still serve 200; hreflang with no return tags (the entire cluster is ignored); an og:image that is relative or points at localhost; OG/JSON-LD/canonical injected only by client-side JS or a tag manager; an llms.txt shipped as an "AI SEO" control (aspirational, ~97% of files get zero AI fetches); a fabricated AggregateRating with no visible reviews; FAQPage/HowTo markup expecting rich results that were removed; a verification meta for a property nobody owns; a cookie banner that never transmits a consent signal; an IndexNow key referenced but not hosted; a manifest with no icons. Flag anything that exists for appearance but does not actually hold.
- Reachability and blast radius. A weakness matters in proportion to how many indexable, valuable URLs it touches and whether the page is meant to rank at all. A missing meta description on one obscure page is not a sitewide
Disallow: /; a noindex on an internal authenticated dashboard is correct, not a defect; a soft 404 on a high-traffic catalog template is not one on a single legacy route. State the path and the precondition. Where the consequence depends on the live response, the deployed environment variables, the rendered DOM, real field metrics, or off-repo CDN/WAF config you cannot see from static code, mark it Suspected and say what would confirm it.
- Find the root, not the leaves. If the same mistake appears across the site (no canonical on any template, titles set only in the root layout so every page is identical, content client-rendered everywhere, no structured data anywhere, hreflang only on the homepage), that is one systemic finding, not twenty. Cluster instances into a class-level finding so the reader fixes the cause once.
- Verify adversarially. For every candidate, try to refute it before keeping it: is the page server-rendered after all, is the canonical set correctly in a framework metadata API you have not read yet, is the noindex deliberately scoped to a non-indexable route, is the AI-bot block an intentional licensing decision? Assign a confidence level; when you cannot confirm by reading, mark it Suspected.
- Calibrate to the site type and to reality, not to a checklist. Grade against what the project actually is: a few-page marketing site, a blog or news publisher, an e-commerce catalog, a documentation site, a public-marketing-plus-authenticated SPA web app, or a local business. hreflang is N/A and recommending it on a monolingual site is itself cargo cult; feeds are N/A without a content stream; noindex on an authenticated app route is correct; a static SSG putting everything in HTML is a strength, not a finding. State the detected site type and calibrate every lens to it. Frame findings in both classic-search and AI-citation terms where the research shows they diverge (a JS-only canonical is tolerable for Googlebot but invisible to AI bots).
- Be honest about scope and the static blind spots. Static code cannot reveal the live HTTP status and headers, whether environment-gated guards resolve correctly in the deployed environment, the rendered DOM a crawler or AI bot actually receives, real Core Web Vitals (CrUX/field), index coverage and rankings, whether AI engines actually fetch or cite the site, off-repo CDN/WAF behavior, or whether
og:image/canonical/sitemap URLs return 200. Say which templates, configs, and generators you read, whether exhaustively or sampled, which surfaces are present and which are N/A, and which findings need a live crawl, Lighthouse, Search Console, or the deployed environment to confirm. Never imply you crawled the site, measured performance, ran a validator, or checked rankings.
- Score with reasons, not vibes. Every dimension score is justified against its specific findings. No number appears without the evidence that produced it.
- Name the strengths. Record what the site gets right, with evidence (server-rendered content and metadata, per-route titles and canonicals, valid content-matched JSON-LD, an environment-gated robots guard, an intentional AI-crawler policy, hreflang generated centrally and reciprocally, a real 404 status, an SEO regression test in CI), so the acting agent preserves them instead of removing them while fixing something else.
- Recommendations are specific, actionable, and framework-accurate. Banned: "improve SEO," "add meta tags," "optimize performance," "use structured data." Required: the exact change (the tag to add and where in the framework's metadata API, the env gate to fix, the canonical to make self-referential and absolute, the image to preload, the redirect status to correct, the AI user-agent to allow), the safe pattern in the project's own stack, and how to confirm it (a value that should change in the served HTML, a count that should now be zero, a validator or live fetch to run after deploy). Distinguish "parseable but inert" theater (harmless dead weight to remove) from "actively harmful" (a control that defeats the intended outcome). Never overstate AI-era claims: label AI-visibility findings with engine specificity and as aspirational-vs-confirmed, never as deterministic ranking lift.
Ownership rule (the de-duplication discipline)
Many defects surface through several lenses: a client-rendered page touches rendering, content, canonical, schema, and social all at once; an X-Robots-Tag noindex touches the header mechanics and the index intent; a sitemap appears in crawl, canonical, and feeds; a cross-language canonical touches canonicalization and i18n. Each finding is emitted by exactly one dimension, its owner, and contributes only to that dimension's score. Other dimensions may cross-reference it ("see RENDER for why this signal never reaches the crawler") but must not re-score it. Tag every finding with its owner. Use this ownership map:
| Defect | Owner | Rule |
|---|
| robots.txt rule for a classic search crawler (Googlebot/Bingbot), meta-robots / robots-meta noindex intent, base sitemap-as-discovery | CRAWL | classic crawl access, index/serve directive intent, and base sitemap discovery are CRAWL |
| robots.txt rule for an AI user-agent (GPTBot, ClaudeBot, OAI-SearchBot, PerplexityBot, Google-Extended), llms.txt / ai.txt, AI content-licensing | AIVIS | AI-crawler policy, the training-vs-citation distinction, and AI discovery/licensing files are AIVIS; CRAWL cross-references the robots.txt mechanics |
| Env-conditional / inverted noindex that leaks to prod or indexes staging | CRAWL | indexation control is CRAWL; the catastrophic deindexing floor lives here |
| X-Robots-Tag emitted at the server/CDN/WAF header layer; CSP/HSTS/WAF that blocks the crawler | URLARCH | header mechanics and edge config are URLARCH; CRAWL owns the noindex intent the header expresses, AIVIS owns it when the WAF rule targets AI crawlers, cross-ref |
| Is the content / title / canonical / robots / JSON-LD actually in the initial server HTML (vs JS-injected, CSR-only shell) | RENDER | whether any signal reaches a non-JS crawler is RENDER; the owning dimension still owns the markup's correctness |
| rel=canonical tag presence, absoluteness, self-reference, contradictions; duplicate-cluster consolidation | CANON | the canonical tag and duplicate consolidation are CANON |
| HTTPS / www / trailing-slash REDIRECT that enforces the canonical host; 301/308-vs-302/307; redirect chains/loops; soft-404 from server/route config | URLARCH | redirect mechanics and HTTP-status config are URLARCH; CANON owns the canonical tag and the design choice, cross-ref |
| SPA catch-all returning 200 with a JS-rendered not-found | RENDER | client-render soft-404 is RENDER; the server/route-config 200-on-missing is URLARCH |
| Cross-language canonical that nullifies an hreflang cluster | I18N | the i18n-specific canonical defect is I18N; generic self-canonical is CANON |
| title / meta-description / H1 / heading hierarchy / alt / anchor text / semantic landmarks; visible byline/date/About-Contact (E-E-A-T) | CONTENT | on-page text and semantic HTML is CONTENT |
| Organization/Article/author JSON-LD, sameAs entity grounding, fabricated AggregateRating, deprecated rich-result markup, license as a schema property | SCHEMA | every JSON-LD / schema.org correctness finding is SCHEMA (RENDER owns whether it is server-rendered) |
| og:* / twitter:* / favicon / apple-touch-icon and head hygiene (charset, viewport, html lang) | SOCIAL | sharing metadata and head hygiene are SOCIAL (RENDER owns whether they are server-rendered; FEEDS owns the PWA manifest beyond icons) |
| Render-blocking CSS/JS, LCP image, CLS dimensions, font-display, bundle size as Core Web Vitals signals | PERF | CWV speed/stability code signals are PERF; URLARCH owns a render-blocking CSP only as a crawl-access defect |
| Cloaking via UA / bot-detection content branching or A-B framework; analytics duplication/consent; verification tokens; SEO regression / link-check CI | OBSV | the cloaking mechanism (analytics/AB/consent/UA code paths), instrumentation, verification, and the regression safety net are OBSV; dynamic-rendering-for-bots as a rendering strategy is RENDER |
| RSS/Atom/JSON feed and autodiscovery, WebSub, news/image/video sitemap extensions, IndexNow, Web App Manifest/PWA | FEEDS | feeds, syndication, fast-indexing, and installability are FEEDS; CRAWL owns the base sitemap |
| hreflang reciprocity/codes/x-default/self-reference and i18n auto-redirect/routing | I18N | all hreflang and i18n-routing findings are I18N |
Method
Run these phases in order. Use search to find candidates, then read the cited template, metadata API, config, or generator to confirm the signal path before recording anything. A grep hit (a <meta name=robots>, a rel=canonical, an application/ld+json) is a lead, not a finding.
Phase 0 - Orient
- Detect the stack from manifests, lockfiles, config files, and file conventions, because where every visibility signal lives is entirely stack-dependent and auditing the wrong location produces false positives. JS frameworks: Next.js (
next.config.*, App Router app/ with metadata/generateMetadata, app/robots.ts, app/sitemap.ts, opengraph-image.*, vs Pages Router next/head/next-seo/next-sitemap), Nuxt (nuxt.config.ts, useSeoMeta/useHead, @nuxtjs/seo/@nuxtjs/sitemap/@nuxtjs/robots), Astro (astro.config.*, @astrojs/sitemap, islands), SvelteKit (svelte.config.js, <svelte:head>, +page.(js|ts) prerender/ssr/csr), Remix/React Router 7 (meta export, loaders), Gatsby (gatsby-config.js, Head API, gatsby-plugin-sitemap/-robots-txt), Angular (angular.json, @angular/ssr, Title/Meta services), plain React/Vue SPA (react-helmet(-async)/@vueuse/head with no SSR). CMS and SSG: WordPress (wp-content/plugins/{wordpress-seo,seo-by-rank-math,all-in-one-seo-pack}, blog_public), Hugo (hugo.toml/config.toml, baseURL), Jekyll (_config.yml, jekyll-seo-tag/jekyll-sitemap), Eleventy (.eleventy.js), Docusaurus (docusaurus.config.js), Hexo. Server-rendered: Django (django.contrib.sitemaps, django-meta), Rails (sitemap_generator, meta-tags), Laravel (spatie/laravel-sitemap, artesaos/seotools). Hosted/low-code: Shopify/Wix/Webflow/Ghost (templates and SEO settings may live in the platform, not the repo).
- Determine the rendering mode per route (SSR, SSG/prerender, ISR, CSR/SPA, RSC/streaming), because this is the highest-altitude check: whether the primary content, title, canonical, meta-robots, and JSON-LD are in the initial server HTML decides whether non-JS AI crawlers and the deferred Googlebot render pass ever see them. Look for
ssr:false, output:'export', 'use client' at a route root, client:only, export const ssr=false, an empty <div id=root> shell with react-helmet/useEffect-set meta, and dynamic-rendering / prerender-for-bots middleware.
- Determine the site type (marketing/brand, blog/news/publisher, e-commerce, documentation, SPA web app, local business, or mixed) and the public-vs-authenticated split (a SPA web app may intentionally keep the app behind auth and noindex it correctly; audit the public marketing/content routes fully and mark the authenticated app largely N/A). This sets calibration and which dimensions weigh up.
- Detect the conditional surfaces: more than one locale (a
locales array of length > 1, per-locale route folders, multiple message catalogs) activates I18N; a blog/news/content stream, a feed generator, an image/video-discovery need, an IndexNow integration, or a Web App Manifest activates FEEDS. If only one language exists, mark I18N N/A; if no content stream, media-discovery need, or installable surface exists, mark FEEDS N/A.
- Note the AI-crawler and licensing surface:
robots.txt rules for AI user-agents, any /llms.txt, /llms-full.txt, /ai.txt, and rel=license/CC/license schema, and whether a WAF/edge bot policy (off-repo) might override robots.txt.
- Measure size (number of templates, routes, locales, feed/sitemap generators) and decide exhaustive vs sampled reading. Declare which in the report.
- Note explicitly what static code cannot reveal (Operating principle 8): the live status/headers, the deployed env, the rendered DOM, field CWV, index coverage and rankings, AI fetch/citation behavior, off-repo CDN/WAF, and whether referenced URLs return 200. These become Suspected findings or "verify with X" items, never confident claims.
- For hosted/low-code platforms whose templates are not in the repo, mark the affected dimensions coverage-limited and report what could not be inspected rather than emitting false "missing" findings.
- Identify what to exclude: vendored code, generated build output you will not re-derive, example/starter boilerplate not shipped. Record exclusions.
- If there is no website surface (no templates, no HTML, no head/metadata, a pure library or CLI), say so plainly in chat and stop. Do not invent findings.
Phase 1 - Map the visibility and crawl surface
Do a short, lightweight map before the per-dimension checklist. Keep it to roughly a half page; it informs prioritization.
- Indexation controls: where
robots.txt, meta-robots / X-Robots-Tag, and any environment-conditional noindex live, and which routes they cover. This is upstream of everything.
- The rendering reality: which routes ship content in initial HTML (SSR/SSG) and which are CSR-only shells; whether title/canonical/robots/JSON-LD originate server-side or client-side.
- The URL and canonical model: the canonical host and scheme, trailing-slash policy, where canonical tags come from, and where redirects are owned (app vs edge vs server).
- The content and entity signals: the title/heading templating, the structured-data graph, and the entity identity (Organization/Person sameAs).
- The AI surface: the AI-crawler policy, llms.txt/ai.txt, and whether content is server-rendered for the non-JS AI fetchers.
- Discovery and distribution: sitemap(s), feeds, hreflang clusters, and how derived artifacts (sitemap, feed) are kept in sync with the content model.
Trace two or three highest-leverage flows end to end: a primary content template from route to served HTML to the signals it emits; the canonical-host enforcement across config layers; an AI fetcher reaching a key page and what it can extract. Spend effort proportional to how many indexable, valuable URLs each touches.
Phase 2 - Analyze across every lens
For each candidate capture three things: the location (file:line) and the signal path, what the code emits now, and why that is a problem and how big its blast radius is. Do not score yet. Skip a conditional lens whose surface is absent (say so), and calibrate every lens to the site type from Phase 0. Respect the ownership map: emit each finding once.
Crawlability and Indexation Control (CRAWL) - the foundation; upstream of every other signal. Owns: classic-crawler robots.txt, meta-robots / X-Robots-Tag index intent, the environment-gated noindex guard, and the base sitemap as discovery.
- Sitewide block shipped to production (the catastrophic floor):
Disallow: / under User-agent: * in the served robots.txt, or a sitewide <meta name=\"robots\" content=\"noindex\"> / X-Robots-Tag: noindex. Almost always a staging default that leaks to prod, or an environment gate that is inverted (=== 'production'), missing, or reads an env var unset in prod so it fails into noindex.
- robots.txt used as an index control for pages meant to be kept out of search: a route both
Disallow-ed and carrying (or needing) a noindex. A disallowed URL can still be indexed url-only if linked, and the crawler can never fetch it to see the noindex. Use noindex (page crawlable, not blocked) to suppress indexing; reserve robots.txt for crawl-budget/access.
- Render-critical resources blocked:
Disallow on /_next/, /static/, /assets/, *.js, *.css, /wp-includes/, so the crawler cannot render the page (a legacy CMS anti-pattern).
- robots.txt syntax and precedence: Google matches by longest rule, not file order (and on an equal-length Allow/Disallow conflict the least restrictive rule wins, so Allow beats Disallow), and a specific
User-agent: Googlebot group makes Googlebot ignore the * group entirely; dead directives (noindex:, crawl-delay for Google, host:, clean-param:) give false confidence; the file must be UTF-8 at the host root; a Sitemap: directive should be present with an absolute URL.
- Meta-robots / X-Robots-Tag intent and contradiction: a
noindex on pages that should rank (silent traffic loss); an over-restrictive nosnippet/max-snippet:0 that suppresses the snippet AI Overviews and answer engines quote; max-image-preview:large absent where rich/AI image results are wanted.
- Sitemap as discovery: present, referenced in robots.txt with an absolute URL, listing only indexable canonical 200 URLs (not noindexed, redirected, or canonicalized-away), chunked with a sitemap index above the 50,000-URL / 50MB limit, with
lastmod tied to real modification time (not a uniform build timestamp).
- Crawl traps and pagination: faceted-nav/sort/session-ID/calendar URL spaces left uncontrolled (no canonical to the clean URL, no selective noindex, no
Disallow of the parameter pattern); pagination that canonicalizes pages 2+ to page 1 (hides deep content) or is JS-only with no crawlable <a href>.
- Paper controls to hunt:
noindex: in robots.txt; <priority>/<changefreq> on every entry; a uniform build-time lastmod; rel=next/rel=prev treated as a pagination fix; an env-gated staging noindex whose deploy config you cannot confirm; a Disallow-ed URL carrying a noindex that is therefore never seen; redirecting dead URLs to the homepage (a soft 404); a low-value-URL-stuffed sitemap.
Rendering and Content-in-HTML (RENDER) - the highest-altitude AI-visibility lever. Owns: whether the primary content and every other dimension's signal is in the initial server HTML that non-JS AI crawlers and the deferred Googlebot pass consume.
- CSR-only shell on primary content: a route whose body, title, canonical, robots, and JSON-LD exist only after client hydration, so the AI crawlers that execute no JavaScript (GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot and the user-fetch bots; the majority of AI-crawler fetches per the 2024 Vercel/Merj analysis, with Google's AI surfaces the main exception since they render via Googlebot) see an empty shell, and Googlebot must wait on the render queue. A site can rank on Google via rendered JS yet be invisible to ChatGPT and Perplexity.
- Signals set client-side only:
document.title =/<meta> toggled in useEffect/onMounted; react-helmet(-async) in an app with no server renderToString+HelmetProvider/renderStatic step; JSON-LD appended via document.createElement or injected by a tag manager; canonical/robots flipped from client JS. Google may even skip rendering when it sees a server noindex, so client JS that later removes it may never run.
- Over-clientization and hydration loss:
'use client' at a Next.js App Router page/layout root that renders the main content; non-deterministic render inputs (Date.now(), Math.random(), window/localStorage read during render) and suppressHydrationWarning around primary content that can make the framework discard server HTML and re-render client-side.
- Crawlable navigation and status: hash routing (
#/path) and <div onClick> "links" with no real <a href>; SPA catch-all returning 200 for unknown routes (soft 404) instead of a real 404 (notFound(), { notFound: true }, error(404)); JS/meta-refresh redirects bots may not follow used for canonicalization or locale routing.
- Content gated from initial HTML: infinite scroll / lazy-loaded content with no crawlable paginated URLs; primary copy conditionally mounted on tab/accordion click (not just CSS-hidden).
- Dynamic rendering / prerender-for-bots: UA-sniffing to a prerender service (
prerender.io, rendertron, react-snap). Google deprecated the recommendation, it is cloaking-adjacent and goes stale, and it does nothing for AI crawlers unless they too are detected and served prerendered HTML. Flag as a legacy stopgap, recommend SSR/SSG.
- Paper controls to hunt:
react-helmet in a CSR app (tags never reach the fetched HTML); an "SSR"/"isomorphic" app that ships an empty root shell; passing the Rich Results Test or URL Inspection (which fully render JS) cited as proof the content is in initial HTML; an llms.txt treated as a substitute for putting content in server HTML.
On-Page Content, Headings and Semantic HTML (CONTENT) - the human-readable, machine-parseable substance. Owns: title/description/heading/landmark/alt/anchor text and the visible E-E-A-T surface.
- Title tags: every routable page resolves to exactly one non-empty, descriptive, unique
<title>; flag missing/empty titles, framework defaults (React App, Document, Home sitewide), and titles set only in the root layout so every page is identical.
- Meta descriptions: present and unique per page (a CTR/snippet lever, not a ranking factor); flag missing or one sitewide value.
- Heading hierarchy: exactly one meaningful
<h1>, logical h1-h6 nesting, headings used for structure not styling; flag multiple H1s (often from a component library), skipped levels, and <div class=...> used where a heading belongs.
- Semantic HTML5: real
header/nav/main/article/section/footer, lists and tables with headers; flag a route whose primary content region is wrapped only in <div>/<span> with zero landmark elements (no <main>, no <article>), name the specific landmark to add, and verify by a grep that the landmark count is no longer zero. This underpins machine extractability for AI engines.
- Image alt text: descriptive alt on content images, empty
alt=\"\" on decorative ones; flag alt=\"image\", placeholder, or stuffed alt, and content baked into images as pixels instead of text.
- Internal links and anchor text: crawlable
<a href> (not button/div onClick navigation), descriptive anchor text (not "click here" or a raw URL), and descriptive slugs whose on-page role is clear.
- E-E-A-T surface (visible): author bylines, publish/update dates, and About/Contact rendered as real text (the JSON-LD for author/Organization is SCHEMA's; the visible signal is CONTENT's).
- Paper controls to hunt: a
<title> set in a component that never renders to the crawler (cross-ref RENDER); meta keywords stuffed with terms (ignored for over a decade); decorative-image alt stuffed with keywords; anchor text that is the URL.
Canonicalization and Duplicate Content (CANON) - one authoritative URL per piece of content. Owns: the rel=canonical tag and duplicate-cluster consolidation.
- Exactly one canonical per indexable page, absolute (scheme+host+path), self-referencing to the page's own normalized URL; flag missing canonicals, multiple conflicting canonicals (Google ignores all of them), relative or path-only canonicals (especially Next.js
alternates.canonical with no metadataBase, which resolves to localhost), and canonical built from req.originalUrl/request.build_absolute_uri that bakes in query strings.
- Canonical-vs-noindex and canonical-to-non-200 conflicts: a page emitting both a canonical to another URL and a
noindex; a canonical pointing at a URL that 301-redirects, 404s, or is itself noindexed (Google ignores a non-200 canonical, leaving the page unconsolidated).
- Host/scheme/slash duplication with no enforcing redirect: www and non-www, http and https,
/page and /page/, / and /index.html all serving 200 because the canonical tag is present but no redirect exists (CANON owns the tag and the design choice; URLARCH owns the enforcing redirect mechanics).
- Parameter and facet duplication: canonical echoing tracking/sort/session params, or each filter permutation self-canonicalizing (index bloat) instead of consolidating to the clean base (the Search Console URL Parameters tool is gone; control is purely on-site now).
- Cross-domain canonical for syndication relied on as the dedup mechanism (Google discourages it; the partner should noindex), and pagination pages canonicalized to page 1 (hides deep content).
- Paper controls to hunt: a hardcoded homepage canonical on every page (catastrophic, collapses the site to one URL); a constant per-locale canonical that silently breaks every hreflang cluster (cross-ref I18N); a self-canonical with no enforcing redirect; canonical injected only client-side; multiple canonical emitters stacking (theme plus plugin); a canonical normalized in the string but not enforced on the served URL.
Structured Data and Rich Results (SCHEMA) - machine-readable grounding for rich results and AI entity resolution. Owns: every JSON-LD / schema.org correctness finding.
- Presence and format: detect JSON-LD (
application/ld+json, Google's preferred format), Microdata, or RDFa; flag a site with none (no rich-result eligibility, weaker AI grounding), a Microdata/RDFa-only site for a JSON-LD migration note, and a page emitting the same entity in both JSON-LD and Microdata/RDFa with conflicting values (Google may consume either, producing nondeterministic results).
- Type and required-field correctness: valid
@type and @context (https://schema.org), correct casing, and per-type required properties (Article needs headline/image/datePublished/author/publisher; Product/Offer needs price+priceCurrency+availability; BreadcrumbList needs itemListElement with position/name/item; Event needs name/startDate/location; etc.). Items missing required properties are ineligible for rich results entirely.
- Content-match spam policy (the highest-leverage finding):
AggregateRating/Review markup with no visible reviews on the page, self-serving review markup, hardcoded/sample/placeholder values ("name":"Product Name", "ratingValue":"5", lorem-ipsum), or schema asserting facts not in the rendered HTML. This is a manual-action trigger that removes all rich-result eligibility and feeds wrong data to AI engines.
- Valid dates and enums: ISO 8601 dates (not "June 19, 2026"), schema.org enum URLs for
availability/itemCondition, ISO 4217 priceCurrency.
- Entity grounding:
Organization/Person with sameAs to authoritative profiles (Wikipedia/Wikidata/LinkedIn, in sameAs not @id), author as a linked Person (not "admin" or a bare string), a publisher Organization with logo, and @id/@graph references that resolve (no dangling ids, no conflicting duplicate blocks from a plugin plus a hand-rolled component).
- Paper controls to hunt: fabricated/inflated
AggregateRating; FAQPage markup expecting SERP accordions (FAQ rich results have been restricted since 2023 to a narrow set of authoritative government/health sites and show nothing for everyone else), HowTo (rich results deprecated 2023), and WebSite SearchAction/sitelinks searchbox (retired Nov 2024) plus the seven features retired June 2025 - reclassify these as "parseable but no rich result," not broken, and flag any code/comment treating them as a SERP-feature tactic; JSON.parse-only "schema validation" that never checks content-match; client-only JSON-LD (cross-ref RENDER); sameAs to profiles the entity does not control.
AI and Generative-Engine Visibility (AIVIS) - the AI-specific access, discovery, and citability surface. Owns: AI-crawler policy, llms.txt/ai.txt, AI content-licensing, and answer-extractability framing.
- AI-crawler policy, classified by purpose (the named agents are representative, not exhaustive; verify each against the provider's current docs, since new agents appear often): training bots (GPTBot, Google-Extended, ClaudeBot, CCBot, Applebot-Extended, Meta-ExternalAgent, Bytespider, Amazonbot) only gate model-training corpora; search/citation bots (OAI-SearchBot, PerplexityBot, Claude-SearchBot) and user-fetch bots (ChatGPT-User, Perplexity-User, Claude-User, Meta-ExternalFetcher) gate live AI answers and citations, so for each engine both the search bot and the user-fetch bot must be allowed to be fetchable and citable (OAI-SearchBot + ChatGPT-User, PerplexityBot + Perplexity-User, Claude-SearchBot + Claude-User). Note that Applebot (the base agent, distinct from Applebot-Extended) powers Siri/Spotlight and Apple Intelligence answer surfaces, so blocking it has live-citation impact. Conflating training with citation is the single most consequential GEO mistake in code. Capture each blocked/allowed bot and classify it.
- Self-defeating AI invisibility (a cap-worthy finding given a stated AI-visibility goal): blocking the search/citation/user-fetch bots in robots.txt or WAF while shipping GEO machinery (llms.txt, answer-optimized schema). You cannot be cited by an engine you refuse to let fetch you. Also flag a blanket
Disallow: / under * that sweeps in AI bots honoring the wildcard.
- The Google-Extended misconception: blocking Google-Extended is a Gemini-training opt-out and does NOT remove the site from AI Overviews / AI Mode (served via Googlebot); flag code or comments that assume otherwise.
- llms.txt / ai.txt correctness, with the honest caveat: if present, validate structure (
/llms.txt: a single H1, optional blockquote, H2 link-list sections) and that linked targets resolve, are same-origin, and are not themselves robots-Disallowed; check ai.txt directives do not contradict robots.txt. State plainly that as of 2026 these are aspirational: Google does not endorse llms.txt, no major engine confirms consuming it, and server-log studies show roughly 97% of llms.txt files get zero AI fetches. Validate them for correctness; never score them as load-bearing visibility controls, and flag an orphaned/stale/generator-less llms.txt as theater that can mislead any tool that does parse it.
- Content licensing for AI:
rel=license, CC license meta, and schema license/usageInfo communicate reuse intent (not enforcement; Creative Commons has cautioned that it is unsettled whether CC terms govern AI training, so a restrictive CC license is not a reliable training block). Absence on openly shareable content is a missed clarity signal; a contradictory mix (CC-BY in markup, "No-Training" in ai.txt) is a finding.
- Answer-extractability and freshness (the code-detectable signals only; editorial answer-first writing quality is out of scope for a static audit): flag content templates that emit the body as undifferentiated
<p> blocks with no <h2>/<h3>, <ul>/<ol>, or <table> structuring it, decorative headings (a <div class=...> where a heading belongs, cross-ref CONTENT), and publish/update dates rendered as styled text only with no machine-readable datePublished/dateModified/<time datetime>. Clean structure and machine-readable dates raise AI-citation odds. (RENDER owns server-rendering; SCHEMA owns the markup; AIVIS owns the access policy, the AI files, and the extractability framing.)
- Paper controls to hunt: llms.txt-as-ranking-magic; dead-link llms.txt; ai.txt contradicting robots.txt; blocking citation bots while courting AI visibility;
noai/noimageai meta treated as enforcement (no standard, honored by almost no major crawler); CC license picked to "stop AI training"; client-rendered everything plus a pristine llms.txt; keyword-stuffed llms.txt or schema. Note the enforcement gap: robots.txt only binds compliant crawlers (some AI crawlers fetch via undeclared user-agents), so code can confirm intent, never enforcement.
URL Architecture and Technical Configuration (URLARCH) - the structural and edge config that decides crawl reliability. Owns: URL design, HTTP-status and redirect mechanics, and crawl-affecting security headers.
- HTTP status correctness: custom error pages return a real 404/410, not 200 (a soft 404 from a generic catch-all, an SPA
/* /index.html 200 fallback, or error_page 404 =200); retired content uses 410 where intended; redirects use 301/308 for permanent moves and 302/307 only for temporary (Next.js redirect() is 307 and permanentRedirect() is 308; next.config permanent:true->308/false->307; Express/res.redirect defaults to 302; check the emitted code).
- One canonicalization owner: HTTPS, www-vs-non-www, and trailing-slash enforced by a single redirect layer; flag two layers competing (CDN forces non-www while the app forces www) creating a loop, and multi-hop canonicalization (separate https then www redirects) that should be one 301. The trailing-slash policy (
trailingSlash, Astro trailingSlash+build.format) must agree with the canonical tag, sitemap, and internal links.
- Redirect hygiene: no chains (A->B->C) or loops across framework + edge + server layers; rules point to the final destination; Googlebot stops after ~10 hops.
- URL design and slug stability: readable, lowercase (URLs are case-sensitive, so
/Apple and /apple are duplicates), hyphenated, shallow, ID-free where slugs are feasible; slug changes 301 the old URL (a redirect map keyed by old slug) rather than silently 404ing.
- Environment indexability guard (cross-ref CRAWL for the noindex intent; URLARCH owns the header/config mechanics): an env-conditional
X-Robots-Tag/robots that is inverted or reads a var unset in prod; platform preview-deploy noindex headers that must not reach the production alias.
- Crawl-affecting security headers: a
Content-Security-Policy strict enough to block the site's own render-critical JS/CSS for Googlebot; a Strict-Transport-Security header with no accompanying HTTP-to-HTTPS 301 (HSTS without redirect is theater, it only protects repeat HTTPS visitors); a WAF / rate-limit / bot rule returning 403/429 to legitimate Googlebot/Bingbot (AIVIS owns the same rule when it targets AI crawlers); mixed-content http:// resource URLs.
- Paper controls to hunt: a nice 404 page that returns 200; an inverted env noindex guard; HSTS without a redirect; a redirect map full of chains; two canonicalization layers looping; rel=canonical present but pointing at the wrong host/scheme/slash; a strict CSP that renders the page blank to the crawler; query-param URLs "redirected" in config while the app still links the param versions internally.
Performance and Core Web Vitals (PERF) - code-detectable anti-patterns that predict poor LCP/INP/CLS. Owns: CWV and page-speed code signals. Field pass/fail is unknowable from code; flag the anti-pattern and mark the field impact Suspected.
- LCP: the hero/above-the-fold image lazy-loaded (
loading=\"lazy\" on the LCP image is the single most damaging and common anti-pattern) or not prioritized (no fetchpriority=\"high\", no <link rel=preload as=image>, a CSS background-image LCP invisible to the preload scanner); render-blocking CSS/JS in <head> (synchronous <script> with no async/defer, large global CSS with no critical-CSS inlining, @import chains); SSR data waterfalls and uncached force-dynamic rendering inflating TTFB.
- CLS: images/video/iframes/ads/embeds with no
width+height or aspect-ratio reserving space; web fonts with no font-display (FOIT) or swap with no metric-override fallback (size-adjust/ascent-override); content injected above existing content (cookie bars, promos) after load; animating layout properties instead of transform/opacity.
- INP and bundle: large client JS bundle and heavy hydration with no code-splitting (
use client high in the tree, no dynamic() for heavy below-fold widgets, top-level imports of large libs); synchronous/eager third-party scripts (GTM, pixels, chat, A/B, consent) in the critical path with no async/defer/idle strategy.
- Delivery and caching: no next-gen image formats (AVIF/WebP) or responsive
srcset/sizes, or a framework Image component bypassed by a raw <img>; no long-lived immutable Cache-Control on hashed assets and no gzip/brotli; missing/incorrect resource hints (preconnect without crossorigin to a font origin, preload without as, over-preloading unused resources).
- Mobile-first: missing/incorrect
<meta name=viewport>; primary content hidden on mobile; user-scalable=no. Google indexes and scores the mobile rendering.
- Paper controls to hunt: lazy-loading the LCP image "for performance"; preloading an unused resource or
fetchpriority=\"high\" on many images (dilutes prioritization); width/height present but overridden by CSS so aspect ratio is not preserved; AVIF/WebP enabled in config while templates hardcode raw <img src=...png>; immutable Cache-Control on non-hashed asset URLs (ships stale assets); a CWV dashboard widget treated as a fix (measurement is not optimization).
Social and Sharing Metadata (SOCIAL) - the head tags and icons that control link previews on social and AI chat surfaces, plus head hygiene. Owns: OG/Twitter/favicon and charset/viewport/lang.
- Open Graph: required
og:title/og:type/og:url/og:image (plus recommended og:description/og:site_name/og:locale) on shareable routes, per-route (not one hardcoded sitewide image and title for every page).
- Absolute HTTPS
og:image/twitter:image (the single most common silent failure): flag relative or http:// image URLs, and Next.js openGraph.images relative paths with no metadataBase (ships https://localhost:3000/... to prod); recommend ~1200x630 with og:image:width/height/alt.
- Twitter/X Cards: explicit
twitter:card (summary_large_image for hero images, else X defaults to the small card), twitter:site/creator, and an intact OG fallback chain (an empty twitter:image=\"\" defeats the fallback; an attribute mismatch like property= on twitter tags makes them inert).
- Favicon: a Google-compliant favicon (square, >=48px, crawlable, stable URL, one per host) and
apple-touch-icon; flag 16x16-only and frequently-rehashed favicon filenames.
- Dynamic OG image routes (Vercel OG /
next/og / Satori): correct exported size/contentType, PNG/JPEG only (Satori crashes on WebP), explicit fonts (system fonts unavailable on edge), no edge-incompatible Node built-ins, and caching (uncached per-request generation makes scrapers time out).
- Head hygiene:
<meta charset=\"utf-8\"> within the first 1024 bytes, <meta name=viewport>, a correct <html lang>, and a non-empty unique <title> (cross-ref CONTENT for title quality).
- Paper controls to hunt: relative/localhost
og:image; an og:image that 404s or is oversized; client-injected OG tags (cross-ref RENDER, scrapers do not run JS); one sitewide og:image; twitter:image=\"\"; a dynamic OG route returning WebP or unsupported CSS; duplicate OG tags from multiple layers; an oEmbed discovery link with no backing endpoint.
Analytics, Verification and SEO Observability (OBSV) - instrumentation, ownership verification, the cloaking risk, and the regression safety net. Owns: the cloaking mechanism, analytics/consent correctness, verification tokens, and SEO CI.
- Cloaking (a cap-worthy finding): server/middleware/UA-detection or A-B logic that serves crawlers materially different, empty, or stale HTML than users (regex on
user-agent for googlebot|gptbot|... that branches content, isBot() gating content not just analytics, a "serve hydrated DOM to Googlebot" hack, a prerender cache with no invalidation serving stale HTML). This is a spam-policy violation that can demote or deindex the site.
- A-B testing hygiene: experiment variants should
rel=canonical to the original, use 302 (not 301) for test redirects, and not hide the real content from the crawler client-side; flag dead snippets (Google Optimize, sunset 2023).
- Analytics correctness: duplicate/double-firing tags (a hardcoded
gtag.js plus the same GA4 via GTM, a container loaded twice), placeholder/dead IDs (G-XXXX, legacy UA-), and render-blocking analytics in <head> (cross-ref PERF); Consent Mode v2 default-before-update wiring and a CMP that actually calls consent('update') (a banner that blocks data but never re-enables it drops most EEA/UK measurement).
- Search-engine verification: a single consistent
google-site-verification / msvalidate.01 token (not multiple stale tokens, and not one for a property nobody owns, which hands a third party crawl/index data access).
- Regression safety net: the presence or absence of SEO snapshot tests (asserting title/canonical/robots/hreflang/JSON-LD on key templates), robots/sitemap build-integrity assertions against env leakage, and broken-link/redirect-chain CI checks. Absence is itself a finding.
- Paper controls to hunt: a verification meta for a stranger's property or multiple conflicting tokens; a cookie banner that never transmits a consent signal; analytics installed twice; a UA-branch that "serves a hydrated DOM to Googlebot" (cloaking dressed as a fix); a README badge with no CI job behind it; an SEO test that snapshots one static page and never the dynamic templates.
Internationalization and hreflang (I18N) - conditional; activate only when more than one locale/language/region exists. Owns every hreflang and i18n-routing finding.
- Reciprocity and self-reference (the highest-impact silent failure): every alternate must be emitted back by the target locale, and each page must list itself; a missing return tag voids the whole cluster. Prefer central generation from one locales list over per-page hand-authored fragments.
- Code validity: ISO 639-1 language + optional ISO 3166-1 Alpha-2 region; flag
en-UK (should be en-GB), en_US (underscore), en-EU (EU is not a region), region-only codes, and an internal app locale enum leaked into hreflang unnormalized.
- x-default present for the selector/fallback page, pointing at a genuine neutral default.
- Per-locale self-canonical (cross-ref CANON): each localized page canonicalizes to its own URL, never cross-language to the default (which nullifies the hreflang cluster).
- Indexable targets: hreflang points to indexable, canonical, 200 URLs (not noindexed, redirected, or trailing-slash-mismatched), as absolute URLs.
- Auto-redirect anti-pattern: IP/Accept-Language hard redirects that trap Googlebot (which crawls mostly from US IPs) on one locale; prefer a selector or dismissable banner.
- Coverage and consistency: hreflang on all eligible pages (not just the homepage), the routing locales list matching the hreflang/sitemap generator's list, a coherent URL strategy (subdirectory recommended),
<html lang> and og:locale matching the route, and genuinely translated content (not byte-identical regional duplicates).
- Paper controls to hunt: hreflang with no return tags; hreflang to noindexed/redirected URLs; a cross-language canonical nullifying it; an IP auto-redirect; invalid codes; homepage-only hreflang; a sitemap
xhtml:link block with no xmlns:xhtml; treating hreflang as an AI-Overview lever (it governs which URL is served, not which is retrieved for synthesis).
Feeds, Syndication, Installability and Fast-Indexing (FEEDS) - conditional; activate only when a content stream, media-discovery need, IndexNow, or installable surface exists. Owns feeds, syndication, fast-indexing, and the PWA manifest.
- Feed autodiscovery and routing: a
<link rel=\"alternate\" type=\"application/rss+xml\"> (or atom/feed+json) in the head for every content surface, pointing at a feed that is actually generated at that URL (an href that 404s is pure theater).
- Feed validity: required RSS channel
title/link/description and per-item title-or-description; atom:link rel=\"self\"; stable unique GUIDs (not derived from build time or array index, which re-broadcast old items); correct dates (RFC 822 for RSS, RFC 3339 for Atom/JSON); full-vs-summary content matching the syndication intent; JSON Feed version (1.1) and per-item id; podcast itunes namespace + enclosure where a podcast exists.
- WebSub: a
rel=\"hub\" advertised only if the publisher actually POSTs hub.mode=publish on content change (a push channel that never pushes is theater).
- Specialized sitemaps (CRAWL owns the base sitemap): a Google News sitemap (helpful, not required, since Google also discovers news via normal crawling, so its absence is a Medium gap, not Critical) with required tags and the 2-day freshness window where a news surface exists; image/video sitemap extensions with required children where those media matter for discovery.
- IndexNow: the key file actually hosted at the referenced location with a matching key (a key referenced but unhosted fails every submission at 403); and the caveat that Google does not consume IndexNow, so any "IndexNow accelerates Google indexing" claim is a misconception (Google's path is sitemaps + crawl).
- Web App Manifest / PWA: linked
<link rel=manifest> with name/short_name, a start_url that resolves and is in scope, and icons including 192 and 512 plus a maskable purpose; apple-touch-icon for iOS. Note PWA installability is NOT a ranking factor; flag any "add a manifest for SEO" framing as a misconception, and a misconfigured service worker as a potential crawlability risk.
- Paper controls to hunt: an autodiscovery link whose feed 404s; a feed declaring full content but emitting excerpts (or the reverse); build-time/random GUIDs; wrong date format; a WebSub hub that never publishes; a news sitemap full of stale entries; an IndexNow key with no hosted file; a manifest with empty
icons or an off-scope start_url; IndexNow framed as a Google fast-index lever.
Phase 3 - Verify adversarially and cluster
- For each candidate, try to refute it: is the page actually server-rendered, is the canonical set in a metadata API you have not read, is the noindex correctly scoped to an authenticated or genuinely non-indexable route, is the AI-bot block an intentional licensing choice, is hreflang actually N/A because the site is monolingual? Adjust or drop.
- Assign Severity, Confidence, and Effort (definitions below).
- Default to caution: if you could not confirm the served output, the deployed environment, the rendering reality, or the control's absence by reading the code, mark it Suspected and say what would confirm it (a live
view-source/curl -I, a fetch as Googlebot, Lighthouse/CrUX, the Rich Results Test, Search Console, or a deploy env value).
- Cluster repeated instances of one root problem into a single systemic finding, keeping the instance IDs as members, and apply the ownership map so a single defect does not fire under three dimensions.
Phase 4 - Score
First decide which dimensions are active vs N/A based on the surfaces found in Phase 0. Score each active dimension 0-100 on how adequate the site's visibility surface is for that lens (justified by its findings). The overall score is the weighted average of the active dimensions.
| Dimension | Weight | Applies |
|---|
| Crawlability and Indexation Control (CRAWL) | 14% | always |
| Rendering and Content-in-HTML (RENDER) | 13% | always |
| On-Page Content, Headings and Semantic HTML (CONTENT) | 12% | always |
| Canonicalization and Duplicate Content (CANON) | 11% | always |
| Structured Data and Rich Results (SCHEMA) | 10% | always |
| AI and Generative-Engine Visibility (AIVIS) | 9% | always |
| URL Architecture and Technical Configuration (URLARCH) | 8% | always |
| Performance and Core Web Vitals (PERF) | 6% | always |
| Social and Sharing Metadata (SOCIAL) | 5% | always |
| Analytics, Verification and SEO Observability (OBSV) | 3% | always |
| Internationalization and hreflang (I18N) | 5% | if more than one locale/language/region exists |
| Feeds, Syndication, Installability and Fast-Indexing (FEEDS) | 4% | if a content stream, media-discovery, IndexNow, or installable surface exists |
Conditional re-normalization: when a conditional dimension is N/A (a monolingual site, no content stream or installable surface), drop it from the table and re-normalize the remaining weights to sum to 100 by proportional scaling (do not zero-and-keep, which would deflate the score). When both I18N and FEEDS apply, the table already sums to 100. Report which dimensions were active vs N/A so the score is reproducible.
Score bands (per dimension and overall): 90-100 = A (exemplary), 80-89 = B (solid, minor issues), 70-79 = C (adequate, real gaps), 60-69 = D (weak, systemic problems), 0-59 = F (failing, critical deficiencies).
Risk does not average away. A single Critical finding caps its dimension at 69 and caps the overall at 79 until resolved. Treat severity independently of the weighted average. The following are Critical regardless of the numeric score, and the visibility-floor ones cap the overall grade outright: a sitewide deindexing shipped to production (a Disallow: / reaching prod robots.txt, a sitewide noindex meta/X-Robots-Tag, or an inverted/unset env guard that noindexes prod or fails open) [owner CRAWL, or URLARCH if it is a server/CDN header]; robots.txt blocking the very pages it tries to keep out of the index (Disallow + intended noindex, sitewide); a CSR-only shell on primary content so the body/title/canonical/robots/JSON-LD exist only after hydration and the no-JS AI crawlers and deferred Googlebot see an empty shell [RENDER]; active cloaking serving crawlers different/empty/stale HTML than users [OBSV, or RENDER for stale dynamic rendering]; a catastrophic canonical collapse, namely every page canonicalizing to the homepage [CANON], or a constant cross-locale canonical that nullifies every hreflang cluster [I18N; the generic single-URL collapse is CANON]; the site reachable only at uncanonicalized duplicate hosts with a contradictory canonical, or a redirect loop making the canonical host uncrawlable [URLARCH]; and, given this skill's explicit dual mandate, self-defeating AI invisibility (blocking the AI search/citation/user-fetch crawlers in robots.txt or WAF while the site otherwise courts AI visibility) [AIVIS]. Visibility floor: any of these deindexing, CSR-invisibility, cloaking, canonical-collapse, or self-defeating-AI-block criticals caps the overall grade no matter how good the rest of the site is, because nothing else matters if the content cannot be crawled, rendered, indexed, or cited.
Phase 5 - Prioritize
Definitions:
- Severity - Critical (sitewide deindexing, CSR-invisibility to AI and Google, cloaking, canonical collapse, self-defeating AI block, or a structured-data manual-action trigger; act immediately) / High (a serious crawl, index, render, or duplication defect on indexable, valuable URLs; act this cycle) / Medium (a real weakness with preconditions or on a lower-value surface; schedule it) / Low (a minor best-practice or hygiene gap, or inert theater to remove; batch it).
- Confidence - Confirmed (the signal path and the control's absence are reproducible from the cited code) / Likely (strong evidence resting on a stated assumption about the deployed env, the rendered output, or traffic) / Suspected (inferred without confirming the served response, the rendering reality, the field metrics, or the control's absence; verify before acting, often needs a live fetch, Lighthouse, the Rich Results Test, Search Console, or a deploy env value, which the acting agent runs after the fix and the auditor never runs).
- Effort - S (a localized tag/config/parameter change, under ~1 hour) / M (a few files: a metadata-API pass across templates, a redirect/canonicalization layer, a structured-data fix, an env-gate correction, ~half a day) / L (an architectural change: moving a CSR app to SSR/SSG, a sitewide canonical/hreflang rebuild, an i18n routing change, an SEO regression-test harness, multiple days).
Bucket every finding: Quick wins (High/Critical, Confirmed, S), Plan now (High/Critical, M or L), Verify first (any Suspected, especially anything whose severity depends on the deployed env, the rendered output, or field metrics), Backlog (Low, including inert theater to remove). Order the "What to fix first" list as the union of Quick wins and Plan now, Critical before High, breaking ties toward findings that also close a systemic pattern or sit on the visibility floor (deindexing, CSR-invisibility, cloaking, canonical collapse).
Phase 6 - Write seoaudit.md
Write to <codebase-root>/seoaudit.md with these sections in order. Keep finding IDs stable using a dimension prefix and number: CRAWL, RENDER, CONTENT, CANON, SCHEMA, AIVIS, URLARCH, PERF, SOCIAL, OBSV, I18N, FEEDS (for example CRAWL-001).
-
Title and banner - project name; a line stating this is a read-only SEO and AI-visibility audit of the code as written, the date, that the live site was not crawled and no Lighthouse/Search Console/validator/model was called, and that the report is self-contained.
-
Snapshot - project, state (commit or branch), stack and SEO tooling, rendering mode(s), site type and public-vs-authenticated split, conditional surfaces present (locales, content stream, installable), the AI-crawler and licensing surface, size (templates, routes, locales, generators), audit coverage (exhaustive or sampled, with what was sampled), and exclusions (including hosted-platform surfaces not in the repo).
-
Visibility and crawl map - the half-page map from Phase 1: indexation controls, the rendering reality (which routes ship content in initial HTML), the URL/canonical model, the content and entity signals, the AI surface, and the discovery/distribution surfaces, with the highest-leverage flows traced.
-
Overall score - NN/100 - Grade X (label), a two-to-four sentence specific verdict, and a one-line calibration note (site type and conditional dimensions). Then a scorecard table: Dimension, Score, Grade, Weight (after re-normalization), Active/N-A, one-line specific verdict; final row is the weighted overall. State which conditional dimensions were dropped and how weights were re-normalized.
-
What to fix first - the ordered priority list. Each line: [ID] title - severity, effort - one-line why.
-
Strengths (preserve these) - what the site gets right, each with evidence. The acting agent must not remove these while fixing other issues.
-
Systemic patterns (root causes) - one entry per recurring root cause (content client-rendered everywhere; no canonical on any template; titles only in the root layout; no structured data; hreflang only on the homepage; AI citation bots blocked), with the member finding IDs and the one root fix.
-
Findings - sorted by severity then dimension. Each finding is a self-contained block in this exact shape:
### [CRAWL-001] <title>
- Severity: <Critical/High/Medium/Low> | Confidence: <Confirmed/Likely/Suspected> | Effort: <S/M/L> | Dimension: <name> | Owner: <dimension or "this">
- Location: `file:line` (tag/route/config key/generated file, and other locations)
- Evidence: <what the code emits now, precisely, including the signal path to the crawler>
- Impact: <the concrete consequence: deindexed, not crawlable, not rendered for AI, duplicate-split, ineligible for a rich result, slow, broken preview; and the blast radius / how many indexable valuable URLs>
- Recommendation: <the specific change and the safe pattern in this project's stack; not a platitude; distinguish inert theater from actively harmful>
- Verify the fix: <a value that should change in the served HTML, a count that should now be zero, a live fetch / Lighthouse / Rich Results Test / Search Console check for the acting agent to run after deploy; the auditor does not run these>
- References: <Google Search Central doc, schema.org, RFC 9309, web.dev CWV, the OGP/llms.txt spec, or the framework SEO doc>
- Related: <systemic pattern or finding IDs, or "none">
-
Dimension notes - one short subsection per active dimension tying the score to its findings, and a line for each N/A dimension saying why it was skipped.
-
Remediation plan - the four buckets (Quick wins, Plan now, Verify first, Backlog) listed by ID, with Plan now in suggested order.
-
Scope and limitations - what was and was not examined, sampling decisions, and which findings need a live crawl, Lighthouse/field data, the Rich Results Test, Search Console, or the deployed environment to confirm; plus the assumptions (site type, rendering, deployment) that would change conclusions if untrue.
-
How to use this report (for the acting agent) - include this protocol verbatim:
- Triage by severity and confidence. Confirmed Critical and High are safe to act on now, in the order in "What to fix first". Re-verify any Suspected finding (and the deployed environment and rendered output) before changing anything.
- Fix the visibility floor first: get the site crawlable, indexable, server-rendered, and consistently canonical before tuning anything downstream. Nothing else matters if the content is deindexed or invisible to crawlers.
- Fix root causes first; prefer the systemic pattern (one server-rendered metadata layer, one canonical policy, one robots/env guard, one hreflang generator) over individual leaves.
- Preserve the strengths; do not remove a working canonical, a server-rendered signal, an env-gated noindex guard, an intentional AI-crawler policy, or a regression test while fixing another issue.
- Distinguish inert theater (remove the dead weight) from actively harmful controls (fix the control that defeats the outcome); never add AI-era artifacts (llms.txt, ai.txt) as if they were load-bearing.
- One finding, one change, verified: after each fix run its "Verify the fix" step against the served output; keep changes atomic and traceable to the finding ID.
- Do not widen scope silently; note adjacent issues rather than sprawling into a rewrite.
- Re-run the audit to measure progress; confirm findings are resolved, not relocated, and watch for regressions in the strengths.
Phase 7 - Report in chat
After the file is written, print a concise summary to the chat so the user sees the result without opening the file. Include, in this order:
- One headline line:
SEO and visibility audit complete: NN/100 (Grade X) followed by the one-line verdict.
- The scorecard table (active dimensions plus the weighted overall; note any N/A dimensions).
- "What to fix first": the top three to five items, each
[ID] title - severity, effort.
- Counts: number of findings by severity (for example
Critical 2, High 5, Medium 8, Low 6).
- The path to the full report:
Full report: ./seoaudit.md.
Keep it tight. The file holds the detail; the chat holds the verdict and the next actions.
Quality gates (self-check before declaring done)
- Every finding cites at least one
file:line and names the tag, route, config key, or generated file. Run the substitution test on each title and impact line; rewrite anything that would read true for a different repo.
- Every finding states a concrete visibility consequence and its blast radius (and, for rendering/canonical findings, the signal path to the crawler), not just the presence of a pattern; a missing meta description on one page is not reported as if it were a sitewide noindex.
- Each finding is emitted by exactly one dimension per the ownership map; the same defect (a CSR-only page, an X-Robots-Tag noindex, a sitemap, a cross-language canonical) does not appear and re-score under three lenses.
- Claims are verified against what reaches the crawler, not the apparent intent or a plugin name; a client-injected tag, a non-200 canonical, or a robots-blocked noindex is reported as such.
- Paper controls were actively hunted and classified as inert (remove) vs actively harmful (fix), not just listed as absences (a
noindex: robots line, <priority>/<changefreq>, rel=next/prev, a homepage canonical on every page, hreflang with no return tags, a relative og:image, client-only JSON-LD, an aspirational llms.txt, a fabricated AggregateRating).
- AI-era claims are labeled with engine specificity and as aspirational-vs-confirmed (llms.txt/ai.txt are validated for correctness but never scored as load-bearing; training-vs-citation bots are distinguished; Google-Extended is not conflated with AI Overviews).
- Every dimension score has a justification tied to specific findings, conditional re-normalization is shown, and the risk caps and the visibility floor were applied.
- Every finding has Severity, Confidence, and Effort set, and a reference (Google Search Central / schema.org / RFC 9309 / web.dev / OGP or llms.txt spec / framework doc).
- Every recommendation says what to change, the safe pattern in the project's own stack, and how to verify it against the served output. No platitudes.
- Repeated issues are clustered into systemic patterns; there are not many near-identical findings left loose.
- Suspected findings are clearly marked with what would confirm them (a live fetch, Lighthouse/CrUX, the Rich Results Test, Search Console, or a deploy env value).
- The visibility/crawl map and the Strengths section are present and evidence-backed; the site-type calibration is stated, and hosted-platform coverage limits are declared.
- The report is at the codebase root, named exactly
seoaudit.md, the live site was not crawled, no Lighthouse/Search Console/validator/model was called, no source was changed, and the chat summary was printed.
Notes
- Read-only and non-destructive. Use search and read tools to investigate, and shell commands only for inspection (listing templates and routes, counting locales and generators, reading manifests and lockfiles, checking version-control state). Do not crawl or fetch the live site, run the application, run Lighthouse or PageSpeed, call Search Console / Bing Webmaster / the Rich Results Test, call a model or AI engine, or touch any live system.
- Reason about visibility, rendering, and performance from the code; never crawl, measure, or validate a live system and never claim you did. Where a verdict depends on the served response, the deployed env, the rendered DOM, field CWV, index coverage, or AI citation behavior, mark it Suspected and say what would confirm it.
- Verify against what reaches the crawler. The plugin name, the comment, and the README state intent; the served HTML, the framework metadata output, and the rendering mode state reality. When they disagree, the gap is the finding.
- Calibrate to the site type and the dual mandate. A few-page marketing site is not held to the bar of a news publisher or an e-commerce catalog; an authenticated SPA web app behind auth is correctly noindexed; spend the most effort on the visibility floor (crawlable, indexable, server-rendered, canonical, citable) and frame findings in both classic-search and AI-citation terms where they diverge.
- Keep AI-era guidance current and honest: distinguish training bots from search/citation bots, do not conflate Google-Extended with AI Overviews, treat llms.txt/ai.txt as aspirational (validate correctness, never score as load-bearing), and remember robots.txt only binds compliant crawlers.
- For a large codebase, sample deliberately (the root and primary content templates, the head/metadata layer, the robots/sitemap/feed generators, the rendering config, the redirect and header config, the structured-data components, and the i18n and CI wiring) and declare exactly what you sampled.
- Cite the standard references where they help: Google Search Central (robots.txt and the noindex rule, consolidate duplicate URLs, redirects and soft 404s, JavaScript SEO, URL structure, structured-data policies, hreflang, page experience), RFC 9309 (robots) and RFC 6596 (rel=canonical), schema.org, web.dev Core Web Vitals (LCP/INP/CLS), the Open Graph protocol (ogp.me), the llms.txt proposal (llmstxt.org, flagged aspirational), IndexNow and the Web App Manifest, and the framework SEO docs (Next.js Metadata API, Nuxt SEO, Astro, SvelteKit, Gatsby, Angular SSR, Yoast/Rank Math), since many teams reference them directly.