seo-audit
The SEO rulebook for sahin.io/angel-investments, with the exact thresholds (title 30–60 characters, description 70–160, one H1, 300-word floor for programmatic pages, Core Web Vitals, image, link, schema and sitemap rules) and the build audit that enforces them (scripts/seo-aio-audit.mjs, baseline data/seo-aio-audit-baseline.json). Use before adding or changing any public page, title, description, heading, schema, internal link or image; when the deploy gate reports a new seo-aio-audit violation; and whenever the owner asks for an SEO audit, SEO review or ranking improvements.
- Source file
.claude/skills/seo-audit/SKILL.md
SEO audit
Adapted from the two most used open SEO skill packs, both MIT licensed:
claude-seo (Copyright (c) 2026
agricidaniel) and marketingskills
(Copyright (c) 2025 Corey Haines). Thresholds below are theirs unless marked
"site". AI-search rules (crawlers, answer-first writing, citations, freshness)
are in the ai-search-optimization skill. How a page's head and JSON-LD are
built is in seo-head-and-schema.
Run it
npx tsx scripts/gate.ts # runs it inside "SEO validation"
node scripts/seo-aio-audit.mjs # after a build (dist/cloudflare)
node scripts/seo-aio-audit.mjs --report # every violation, not only new ones
node scripts/seo-aio-audit.mjs --update-baseline # after fixing old debt
The audit reads every indexable route in dist/cloudflare/seo-route-manifest.json
and fails on a violation that is not in data/seo-aio-audit-baseline.json.
The baseline is the old debt: it may only shrink. Fix a new violation rather
than adding it to the baseline. Only add one when the owner accepts it, and
say so in the commit message.
For the live site, use the seo-aio-auditor agent (read-only).
Rules the audit enforces
| Rule id | Check | Threshold |
|---|---|---|
html-size |
Googlebot reads only the first 2 MB of HTML | ≤ 2,000,000 bytes |
canonical |
One absolute, self-referencing canonical | equals the route's canonical URL |
title-length |
<title> |
30–60 characters (fitPageTitle budget) |
description-length |
meta description | 70–160 characters; aim for 120–160 |
title-duplicate / description-duplicate |
unique across indexable pages | no repeats |
h1-count |
headings | exactly one <h1> |
heading-skip |
heading outline | no skipped level (h2 → h4) |
thin-content |
main-content words (programmatic floor) | ≥ 300 words |
img-alt |
every <img> has alt (alt="" only for decoration) |
100% |
img-dimensions |
width and height on every <img> |
100% |
favicon-link |
<link rel="icon"> in the head |
present |
jsonld |
JSON-LD parses, one @graph per page |
exactly one block |
deprecated-schema |
no HowTo, SpecialAnnouncement, ClaimReview, CourseInfo, EstimatedSalary, LearningVideo, VehicleListing |
none |
article-fields |
Article has headline, image, author, datePublished, dateModified |
all present |
article-og-type |
Article pages use og:type article |
match |
date-consistency |
the page entity's dateModified equals the sitemap <lastmod> |
same day |
orphan |
at least one link from another indexable page | ≥ 1 inbound |
The AI-search rules (h1-early, article-citations, visible-date,
robots-ai-crawlers) are explained in ai-search-optimization.
Rules checked by hand
Technical
- Redirects: one hop at most, 301 for permanent moves (
server/publicRouting.ts). - URLs: lowercase, hyphens, no content in query strings, one trailing-slash
policy, under 100 characters (
public-url-conventions). - Title, description, canonical, robots and JSON-LD are in the server HTML, never added by JavaScript (Astro static output does this; keep it that way).
- No
history.pushStatethat hijacks the back button. It became a Google spam policy on 2026-04-13. - Touch targets at least 24×24 CSS px (WCAG 2.2); viewport without
maximum-scaleoruser-scalable=no.
Performance (field data, 75th percentile; FID is retired, use INP)
| Metric | Good |
|---|---|
| LCP | ≤ 2.5 s |
| INP | ≤ 200 ms |
| CLS | ≤ 0.1 |
Lighthouse lab numbers stand in when CrUX has no data.
On-page
- Title: query first, brand last, important words in the first 50 characters, aligned with the H1. No year or "free" unless the page delivers it.
- Description: does not repeat the title, no templated "title + stock CTA".
- The page's main query appears in the first 100 words.
- Quick wins are pages ranking 8–20 in Search Console (
pnpm gsc:report). Change one page's title at a time and wait 3–4 weeks before judging.
Content and E-E-A-T
- Who, How, Why: a visible author (Sahin, linked to /about), how the page was made (data source, date), and a reason a person would want it.
- Visible "Updated" date that matches
dateModified(seeai-search-optimization). - Readability: sentences of 15–20 words, paragraphs of 2–4 sentences.
- Word count is not a ranking factor, but under 300 words on a programmatic page means it adds nothing a list row does not.
Programmatic pages (companies, investors, categories, founders, news)
- Each page must be worth publishing on its own: unique facts, not the same paragraph with the name swapped. Under 40% unique text is thin; under 30% is a stop.
- Never
noindexa page to fix it (owner rule, 2026-10-16,standing-rules). Make it unique: sourced facts, a summary written from its own data, a different angle. - Records with more than 80% of the same fields are duplicates: give each its own facts and angle.
- Two families on the same slugs (
/sectors/{slug}and/market/{slug}) must each answer a different question, both indexed. - Roll out more than 100 new pages in batches and review a 5–10% sample.
- Each page links 3–5 related pages plus its breadcrumb trail.
Schema
- JSON-LD only, in the server HTML, matching what the page shows.
- Use:
WebSite,Person,ProfilePage,Organization,Article,BreadcrumbList,ItemList,CollectionPage,Dataset. FAQPagerich results are gone for sites like this one (claude-seo and marketingskills report full retirement on 2026-05-07). KeepFAQPageonly where the questions are visible; it is harmless and AI engines read it.HowTo(2023) and the sitelinks search box (2024) are retired; never add them.- Validate a changed page with the Rich Results Test and validator.schema.org.
Links
- Descriptive anchor text, never "click here".
- Hub and spoke: the hub links every spoke, every spoke links back, and related spokes link each other.
- Long articles: 5–10 internal links; cite 2–3 authoritative external sources.
Images
- Alt text 10–125 characters that describe the image.
- Weight: thumbnails under 50 KB, content images under 100 KB, hero under 200 KB. AVIF or WebP first.
loading="lazy"only below the fold, never on the LCP image; the LCP image getsfetchpriority="high".
Sitemap and indexing
- Only 200-status, canonical, indexable URLs (
buildCanonicalSitemap). lastmodis the date the page's content last changed, not the build date.- Under 50,000 URLs and 50 MB per file.
- IndexNow runs after each deploy (
scripts/indexnow-submit.ts). - Compare indexed against intended pages monthly in Search Console.
Report format
Rank findings by impact. For each: the URL in full
(sahin.io/angel-investments/...), what was seen, the rule and threshold, and
the file that produces it.