---
name: claude-seo-drift
description: Capture baselines of SEO-critical page elements, diff current state against them, and classify regressions by severity. Git for on-page SEO. Use when the user says "SEO drift", "baseline", "did anything break", "SEO regression", "before and after", or "deployment check". Not for one-off page audits with no baseline (use claude-seo-page) or full technical crawls (use claude-seo-technical).
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: rewritten from patterns in AgriciDaniel/claude-seo (MIT)
  category: SEO & AEO
---

# SEO Drift Monitor

You cannot prove a deploy broke SEO without a snapshot from before the deploy. Capture a baseline when the page is known good, diff against it after every change, and classify what moved.

## Operations

| Request | Operation |
|---|---|
| "baseline this page" | Capture snapshot, save to `seo-baselines/` |
| "compare" / "did anything break" | Fetch current state, diff against latest baseline |
| "history" | List stored baselines and past comparison results for the URL |

## What a baseline captures

Fetch the URL and record all of these in one JSON file:

| Field | Content |
|---|---|
| `url`, `captured_at` | Normalized URL, ISO timestamp |
| `status_code` | HTTP status (capture even 4xx/5xx, status is a tracked field) |
| `title`, `meta_description` | Exact text |
| `canonical`, `meta_robots` | href and content values |
| `h1`, `h2`, `h3` | Arrays, in document order |
| `schema` | All JSON-LD blocks, parsed |
| `open_graph` | og:* tag map |
| `html_hash`, `schema_hash` | SHA-256 of body and of serialized schema |
| `cwv` | Core Web Vitals if a PageSpeed key is available, else null |

Storage: `seo-baselines/<host>/<path-slug>.json`, one file per capture, timestamp in the filename. Keep them in the repo so history rides along with deploys.

URL normalization before matching: lowercase scheme and host, strip default ports, sort query parameters, drop UTM parameters, strip trailing slash.

## Baseline workflow

1. Validate the URL. Refuse private IPs, loopback, and cloud metadata endpoints.
2. Fetch the page over HTTPS with TLS verification on.
3. Extract every field in the table above.
4. Compute the hashes.
5. Write the JSON file and report the path plus a one-line summary of captured elements.

## Compare workflow

1. Load the most recent baseline for the normalized URL (or a specific file if the user names one). None found: say so and offer to create one, do not compare against nothing.
2. Fetch and parse the current state the same way.
3. Apply the comparison rules. Read references/comparison-rules.md for the full rule set with severities and fixes.
4. Report every triggered rule: field, old value, new value, severity, recommended action. Save the comparison result next to the baseline as `<name>.compare.json`.
5. Route follow-ups to siblings: schema changes to claude-seo-schema, CWV or canonical or noindex issues to claude-seo-technical, title/meta/OG changes to claude-seo-page.

Severity meanings: CRITICAL is an SEO-breaking change, act immediately. WARNING needs investigation within a week. INFO is awareness, may be intentional.

## Verification

After capturing a baseline, run `python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d['title'], d['status_code'])" <file>`. Expect the real page title and status printed with no exception. If it fails, the snapshot is malformed, recapture before relying on it.

## Good vs bad

**Good:** Pre-deploy baseline, post-deploy compare, report reads "canonical changed from self to staging URL: CRITICAL, restore before traffic drops."
**Bad:** Traffic dropped, no baseline exists, and the report guesses at what "probably changed". Without a snapshot you have opinions, not diffs. Capture a baseline now so next time you have evidence.

## Footguns

- **Comparing different URL variants.** `example.com/page` and `example.com/page?utm_source=x` must normalize to the same key or you get false CRITICAL diffs. Always normalize before matching.
- **Baseline captured during an incident.** A snapshot of a 500 page becomes your "known good". Check `status_code` is 200 before treating a baseline as the reference for regressions.
- **Missing CWV treated as regression.** If the baseline has `cwv: null`, skip CWV rules instead of flagging a change.
- **Fetching through redirects silently.** Record the final URL and status chain. A new 301 on the page is itself a finding, not transparent plumbing.

## Completion checklist

- [ ] URL validated and normalized before fetch
- [ ] All baseline fields captured, hashes computed
- [ ] Snapshot file written under `seo-baselines/` and verified parseable
- [ ] Compare runs the full rule set, not just fields that look interesting
- [ ] Every finding has old value, new value, severity, action
- [ ] Sibling skill recommended for each CRITICAL finding

Any box unchecked: not done. Fix or say so.
