Writing rule
When this skill writes a durable artifact, read .claude/rules/writing-standard.md. Use the controlled technical writing profile.
/seo-audit — Technical SEO Analysis
Deep-dive SEO audit against Google's best practices. Checks on-page SEO, technical SEO, and social sharing metadata. Invoke when /launch-check's SEO row shows WARN or FAIL.
See also: /geo-audit for the LLM/agent-discoverability sibling check — llms.txt, AGENTS.md, AI-crawler directives in robots.txt, JSON-LD citation metadata, token economics. /seo-audit covers the Googlebot audience; /geo-audit covers the LLM-crawler and coding-agent audience. The two run independently; /launch-check fans out to both at milestone boundaries.
Process
Step 1: On-page SEO
Check each key page (index, about, pricing, blog, product pages) for:
<title> tag (exists, unique per page, 50-60 chars)
<meta name="description"> (exists, unique, 150-160 chars)
<h1> tag (exactly one per page, contains target keyword)
- Heading hierarchy (h1 → h2 → h3, no gaps)
- Internal linking between pages
- Image alt text containing relevant keywords (not keyword-stuffed)
Step 2: Technical SEO
robots.txt at root (exists, not blocking important pages)
sitemap.xml at root (exists, lists all public pages, submitted to Search Console)
- Canonical URLs (
<link rel="canonical"> on each page)
- 404 page exists and returns HTTP 404 (not 200)
- Redirects: any redirect chains? (301 → 301 → page)
- URL structure: clean, readable, no query-string-based routing for content pages
- Mobile viewport meta tag:
<meta name="viewport" content="width=device-width, initial-scale=1">
Step 3: Social sharing (Open Graph + Twitter Cards)
og:title, og:description, og:image, og:url on key pages
og:image dimensions (1200x630 recommended)
- Twitter card meta tags (
twitter:card, twitter:title, twitter:description, twitter:image)
- Favicon and apple-touch-icon
Step 4: Structured data
- JSON-LD or Microdata for relevant schemas (Organization, Product, FAQ, BreadcrumbList, Article)
- Check validity: are required properties present?
Step 5: Output
SEO AUDIT — <project> @ <sha>
| # | Area | Status | Finding |
|----|------|--------|---------|
| S1 | Title tags | PASS | All 5 pages have unique titles (52-58 chars) |
| S2 | Meta descriptions | WARN | /pricing missing meta description |
| S3 | robots.txt | PASS | Exists, allows crawling of public pages |
| S4 | sitemap.xml | FAIL | Not found at /sitemap.xml |
| S5 | Open Graph | WARN | og:image missing on /blog/* pages |
| S6 | Structured data | PASS | Organization + FAQ schema on homepage |
| S7 | Mobile | PASS | Viewport meta tag present |
| S8 | Canonical URLs | PASS | All pages have canonical links |
SEO readiness: GOOD (1 fail, 2 warnings — fix sitemap before launch)
Persist the run + render trend
After printing the findings table, persist via the shared audit-history lib so the SEO trend across runs becomes legible. See docs/agdr/AgDR-0019-audit-artefact-persistence.md.
Resolve project name + score + verdict
<project-name> from apexyard.projects.yaml (or basename + /handover reminder if unregistered).
Score: score = max(0, 100 - 25*critical - 10*high - 3*medium - 1*low). Verdict by worst-severity: critical/high → fail, medium → conditional, low/none → pass. Legacy "SEO readiness" two-state (GOOD/NEEDS WORK) maps via finding count: any high → fail/conditional per the table, none → pass.
Persist + render
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-audit-history.sh"
# Lowercase severity in the payload — the lib expects critical/high/medium/low/info.
payload=$(mktemp); cat > "$payload" <<'EOF'
{
"schema_version": 1,
"findings": [
{"id": "S3", "severity": "high", "status": "open", "summary": "og:image missing on /blog/* templates"},
{"id": "S4", "severity": "high", "status": "open", "summary": "sitemap.xml not found at /sitemap.xml"},
{"id": "S6", "severity": "medium", "status": "open", "summary": "robots.txt allows everything; missing Sitemap: directive"}
]
}
EOF
# Body: per templates/audits/seo-audit.md
body=$(mktemp); cat > "$body" <<'EOF'
... (filled-in body — findings table + Recommended priority) ...
EOF
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
audit_run_persist "<project-name>" "seo-audit" "$ts" "fail" 60 "$body" < "$payload"
rm -f "$payload" "$body"
audit_render_trend "<project-name>" "seo-audit" 5
Opt-in commit
touch projects/<name>/audits/seo-audit/.audit-history-tracked
Rules
- Auto-PASS for non-web projects. APIs, CLIs, libraries, backend-only services don't need SEO.
- Focus on technical SEO, not content strategy. Don't audit keyword targeting or content quality — that's a marketing call, not a technical check.
- Check the built output if available (
dist/, build/, .next/), not just source — SSR frameworks may generate meta tags at build time.
- Prioritize by indexing impact. Missing sitemap > missing og:image > missing structured data.
- Always persist via the lib. The persist step runs regardless of opt-in commit state.
- Severity vocabulary in the JSON is lowercase. The lib expects
critical/high/medium/low/info.
Part of ApexYard — multi-project SDLC framework for Claude Code · MIT.
1---2name: seo-audit3description: Technical SEO audit — meta, OG, sitemap, robots.txt, structured data, mobile, CWV readiness. Deep-dive for /launch-check SEO.4---56## Writing rule78When this skill writes a durable artifact, read .claude/rules/writing-standard.md. Use the controlled technical writing profile.910# /seo-audit — Technical SEO Analysis1112Deep-dive SEO audit against Google's best practices. Checks on-page SEO, technical SEO, and social sharing metadata. Invoke when `/launch-check`'s SEO row shows WARN or FAIL.1314> **See also**: `/geo-audit` for the LLM/agent-discoverability sibling check — `llms.txt`, `AGENTS.md`, AI-crawler directives in `robots.txt`, JSON-LD citation metadata, token economics. `/seo-audit` covers the Googlebot audience; `/geo-audit` covers the LLM-crawler and coding-agent audience. The two run independently; `/launch-check` fans out to both at milestone boundaries.1516## Process1718### Step 1: On-page SEO1920Check each key page (index, about, pricing, blog, product pages) for:2122- `<title>` tag (exists, unique per page, 50-60 chars)23- `<meta name="description">` (exists, unique, 150-160 chars)24- `<h1>` tag (exactly one per page, contains target keyword)25- Heading hierarchy (h1 → h2 → h3, no gaps)26- Internal linking between pages27- Image alt text containing relevant keywords (not keyword-stuffed)2829### Step 2: Technical SEO3031- `robots.txt` at root (exists, not blocking important pages)32- `sitemap.xml` at root (exists, lists all public pages, submitted to Search Console)33- Canonical URLs (`<link rel="canonical">` on each page)34- 404 page exists and returns HTTP 404 (not 200)35- Redirects: any redirect chains? (301 → 301 → page)36- URL structure: clean, readable, no query-string-based routing for content pages37- Mobile viewport meta tag: `<meta name="viewport" content="width=device-width, initial-scale=1">`3839### Step 3: Social sharing (Open Graph + Twitter Cards)4041- `og:title`, `og:description`, `og:image`, `og:url` on key pages42- `og:image` dimensions (1200x630 recommended)43- Twitter card meta tags (`twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`)44- Favicon and apple-touch-icon4546### Step 4: Structured data4748- JSON-LD or Microdata for relevant schemas (Organization, Product, FAQ, BreadcrumbList, Article)49- Check validity: are required properties present?5051### Step 5: Output5253```54SEO AUDIT — <project> @ <sha>5556| # | Area | Status | Finding |57|----|------|--------|---------|58| S1 | Title tags | PASS | All 5 pages have unique titles (52-58 chars) |59| S2 | Meta descriptions | WARN | /pricing missing meta description |60| S3 | robots.txt | PASS | Exists, allows crawling of public pages |61| S4 | sitemap.xml | FAIL | Not found at /sitemap.xml |62| S5 | Open Graph | WARN | og:image missing on /blog/* pages |63| S6 | Structured data | PASS | Organization + FAQ schema on homepage |64| S7 | Mobile | PASS | Viewport meta tag present |65| S8 | Canonical URLs | PASS | All pages have canonical links |6667SEO readiness: GOOD (1 fail, 2 warnings — fix sitemap before launch)68```6970## Persist the run + render trend7172After printing the findings table, persist via the shared audit-history lib so the SEO trend across runs becomes legible. See `docs/agdr/AgDR-0019-audit-artefact-persistence.md`.7374### Resolve project name + score + verdict7576`<project-name>` from `apexyard.projects.yaml` (or basename + `/handover` reminder if unregistered).7778Score: `score = max(0, 100 - 25*critical - 10*high - 3*medium - 1*low)`. Verdict by worst-severity: critical/high → `fail`, medium → `conditional`, low/none → `pass`. Legacy "SEO readiness" two-state (GOOD/NEEDS WORK) maps via finding count: any high → `fail`/`conditional` per the table, none → `pass`.7980### Persist + render8182```bash83source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-audit-history.sh"8485# Lowercase severity in the payload — the lib expects critical/high/medium/low/info.86payload=$(mktemp); cat > "$payload" <<'EOF'87{88 "schema_version": 1,89 "findings": [90 {"id": "S3", "severity": "high", "status": "open", "summary": "og:image missing on /blog/* templates"},91 {"id": "S4", "severity": "high", "status": "open", "summary": "sitemap.xml not found at /sitemap.xml"},92 {"id": "S6", "severity": "medium", "status": "open", "summary": "robots.txt allows everything; missing Sitemap: directive"}93 ]94}95EOF9697# Body: per templates/audits/seo-audit.md98body=$(mktemp); cat > "$body" <<'EOF'99... (filled-in body — findings table + Recommended priority) ...100EOF101102ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")103audit_run_persist "<project-name>" "seo-audit" "$ts" "fail" 60 "$body" < "$payload"104rm -f "$payload" "$body"105106audit_render_trend "<project-name>" "seo-audit" 5107```108109### Opt-in commit110111```bash112touch projects/<name>/audits/seo-audit/.audit-history-tracked113```114115## Rules1161171. **Auto-PASS for non-web projects.** APIs, CLIs, libraries, backend-only services don't need SEO.1182. **Focus on technical SEO**, not content strategy. Don't audit keyword targeting or content quality — that's a marketing call, not a technical check.1193. **Check the built output** if available (`dist/`, `build/`, `.next/`), not just source — SSR frameworks may generate meta tags at build time.1204. **Prioritize by indexing impact.** Missing sitemap > missing og:image > missing structured data.1215. **Always persist via the lib.** The persist step runs regardless of opt-in commit state.1226. **Severity vocabulary in the JSON is lowercase.** The lib expects `critical`/`high`/`medium`/`low`/`info`.123124---125126*Part of [ApexYard](https://github.com/me2resh/apexyard) — multi-project SDLC framework for Claude Code · MIT.*