# SEO Review

> Audit and fix technical SEO and AEO (answer-engine optimisation) for a website: redirect and canonical conflicts, robots.txt and sitemaps, structured data, indexability of staging and internal subdomains, and Google Search Console coverage. Code fixes assume Next.js App Router; the diagnostic phase works on any stack. Trigger phrases include "SEO audit", "SEO review", "AEO audit", "how are we doing on SEO", "why isn't my site indexed", "we're not showing up in Google", "check Search Console", "fix our canonicals", "sitemap isn't working", "make the docs indexable", "how do we show up in ChatGPT".

- Skill: `reidworks-io/seo-review` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add reidworks-io/seo-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/reidworks-io/seo-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: reidworks-io (https://skillmd.com/u/reidworks-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/reidworks-io/seo-review

---


# 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.

```bash
# 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.

```bash
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

```bash
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.

```bash
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.

