SEO Review
Find and fix the technical reasons a site is not being found, by search engines and by AI answer engines. Diagnose against the live site first, fix in code second, verify in the build output third.
Core principle
Never trust the source. Verify what is actually served.
Almost every defect this skill is built from was invisible in code and obvious in curl
output or in built HTML. Read source to understand intent. Verify against the live site
and the build to find bugs.
Instructions
Work the four phases in order. Do not skip ahead to code: domain-level problems invalidate everything downstream, and reporting a canonical fix while the domain still redirects wrongly wastes the user's time.
Phase 1 — Interrogate the live site
Run before reading any source.
# Which host actually serves? Follow nothing.
curl -sI https://example.com/ | grep -iE "HTTP/|location"
curl -sI https://www.example.com/ | grep -iE "HTTP/|location"
# Does a deep path keep its path, or collapse to the homepage?
curl -sI https://www.example.com/blog/a-post | grep -iE "HTTP/|location"
# robots and sitemap must be 200 on the SERVING host, not redirects
curl -sIL https://example.com/robots.txt | grep -iE "HTTP/|content-type"
curl -sIL https://example.com/sitemap.xml | grep -iE "HTTP/|content-type"
# Do canonicals agree with the host serving them?
for p in / /about /blog /pricing; do
printf "%-20s " "$p"
curl -s "https://example.com$p" | grep -o '<link rel="canonical" href="[^"]*"' | sed 's/.*href="//;s/"//'
done
# Titles, descriptions, structured data
curl -s https://example.com/ | grep -oE '<title>[^<]*</title>|<meta name="description"[^>]*>'
curl -s https://example.com/blog/a-post | grep -o '"@type":"[^"]*"'
Check the www/apex relationship first. The highest-impact bug, and common: the apex
redirects to www (or the reverse) while every canonical, sitemap URL and JSON-LD url
points at the other one. The crawled page says "my canonical is X", and X redirects
straight back. Also confirm the status is 308/301 and not 307/302 (temporary
redirects do not consolidate ranking signals), and that it preserves the path.
Fix this in the hosting layer, not in code. On Vercel: Settings → Domains. Clear the redirect on the domain you want primary first, then add the redirect on the other one. The reverse order creates a redirect loop and takes the site down.
Enumerate subdomains. Staging, docs, demo, and self-hosted internal tools are all routinely reachable and crawlable without anyone intending it.
for h in www docs staging app ops crm api demo skills; do
printf "%-10s " "$h"
curl -sI "https://$h.example.com/" -o /dev/null -w "%{http_code}\n" --max-time 5
done
Decide index-or-noindex deliberately for each. When an internal tool is exposed, say plainly that noindex is not access control — recommend auth, an IP allowlist or a tunnel, and treat the robots directive as tidying up afterwards.
Phase 2 — Audit and fix the code
find app -name "sitemap.ts" -o -name "robots.ts" # missing entirely?
grep -rl '"use client"' app/**/page.tsx # these cannot export metadata
grep -rn "datePublished\|dateModified" app/ # check the format is ISO 8601
See references/nextjs-patterns.md for the recurring bugs and their fixes: client
components silently inheriting the root layout's metadata, invalid JSON-LD dates, the
timezone off-by-one you introduce when fixing them naively, the fail-open robots guard
for staging environments, and the structured data worth adding.
Phase 3 — Review Google Search Console
Requires browser automation (the UI is authenticated). Full workflow, including DNS
verification and how to read the coverage report, in references/search-console.md.
Two rules that matter most:
- Insist on a Domain property, never URL-prefix. URL-prefix treats apex and www as separate sites, which is exactly the split being fixed.
- Open the URL lists. Never report from the summary counts. In one audit "Crawled, currently not indexed: 5" turned out to be two font files, a JS chunk, a legacy redirect and an ops subdomain — zero real content pages. The summary implied a content quality problem; the URLs showed the opposite.
Phase 4 — Verify in the build output
Do not report success from source. Check what the build generated.
pnpm build
cat .next/server/app/robots.txt.body
sed 's|<url>|\n<url>|g' .next/server/app/sitemap.xml.body | grep -o '<loc>[^<]*</loc>'
grep -oE '<link rel="canonical"[^>]*>' .next/server/app/some-page.html
grep -o '"datePublished":"[^"]*"' .next/server/app/blog/post.html
# Confirm environment guards behave in both directions
VERCEL_ENV=preview pnpm build && cat .next/server/app/robots.txt.body # expect Disallow: /
This step is not ceremony. It caught a date fix that looked correct in source, passed typecheck, and emitted the wrong day.
Then run the project's own gates (lint, typecheck, test) before committing, and a
full build before opening a PR.
Reporting
Lead with what is broken and what it costs, ranked by impact: domain and redirect issues first, then indexability, then structured data, then content. Not a checklist.
State explicitly what was not done and why. Copy changes against a canonical messaging doc, design changes owned by a designer, and anything needing credentials that are unavailable are all legitimate hand-backs, but name them rather than leaving them silently undone.
When the user asks for something counterproductive — a common one is "noindex the docs" for a developer tool, where docs are the highest-value indexable asset — say so in a sentence or two, then build what they asked and make the reversal a one-line change.
On AEO
At the retrieval layer, AEO is roughly 80% ordinary SEO: every major answer engine grounds on a conventional index, so not indexed means not retrievable. Fix SEO first. There is no shortcut around it.
At the selection layer it genuinely differs. See references/aeo.md for what to act on,
which published statistics to distrust and why, and how to measure at small scale.