I ran the skill from this article on ten SEO keywords and the pair it caught looks wrong:
seo audit x seo audit checklist 0 shared (separate)Two keywords that sit three words apart in text share nothing in the actual Google results. One targets audit tools. The other targets tutorial posts that explain how to run an audit. If you saw "seo audit" and "seo audit checklist" in the same keyword list and planned one page, you have already made the mistake this article exists to prevent.
The fix is to let Google do the clustering. Whether a keyword belongs on the same page as another is not a question about the words - it is a question about the results. This skill scores every keyword pair by how many of the same URLs Google shows, then turns the verdicts into a hub-and-spoke content plan with an internal link matrix.
The short answer
Two keywords that return the same Google results should be targeted by one page. Two keywords with different results need separate pages. That is the whole method, and it scales: collect the top-10 organic URLs for each keyword, count shared URLs per pair, and apply four thresholds.
Shared results | Verdict | What you do |
|---|---|---|
7-10 | Same post | Merge into one page, higher-volume keyword leads |
4-6 | Same cluster | Group under one spoke cluster; separate posts when volumes differ |
2-3 | Interlink | Adjacent clusters; add cross-links between the posts |
0-1 | Separate | Different clusters, or out of this pillar entirely |
Everything below is the machinery around that table: where the data comes from, how the threshold boundaries are argued, and what to do once you have the verdicts. The skill ships as one SKILL.md and one zero-dependency python3 script, so the whole thing runs on any machine that has Codex.
Google is the judge because "similar" is a guess
Text-based clustering tries to predict what Google will do from the keywords themselves - a guess with no way to check it. SERP overlap skips the guess and measures what actually ranks. Two consequences make the measurement worth it:
Near-identical text can have zero overlap. In my run, "seo audit" and "seo audit checklist" shared 0 results. "Technical seo" and "technical seo audit" shared 1. The first pair looks like a core keyword plus its long-tail and the second looks like a same-idea pair. Both are separate pages in Google's eyes, and separate pages are what the rankings expect.
Near-unrelated text can overlap. "Seo audit" and "technical seo audit" shared 5 of 9 results - more than either shares with its near-twin. Both planes are answered by the same set of audit-page results. That pair is a cluster, and it is the pattern that tells you which content must link to which and what belongs under one pillar.
Google's ranking decisions are not perfect either, which is why the method is a measurement, not an oracle. But it is a measurement anyone can run with a search session and a text file, and it beats text similarity in the direction that matters: you are planning pages against the same judge that will rank them.
The skill's expansion and classification steps (below) fill in the underlying keyword list; the overlap scoring is what turns that list into structure.
The thresholds, argued
0-1 gives you nothing to build on, so those keywords leave the pillar. At 2-3 the SERPs overlap a little and Google treats them as related-but- different, which is the natural source of cross-links: two posts that answer related but distinct questions. At 4-6 Google is ranking the same two or three resources for both, so the keywords belong to the same subtopic area - separate posts when the volume or intent differs, an umbrella when they do not.
7-10 is the decision that people resist most: merging keywords into one page feels like losing content when it is actually the opposite. If the same ten URLs answer both queries, writing two pages means your own posts compete with each other for the same ranking slots.
The 3-4 boundary is where the scores get argued in the original methodology and the skill keeps that tie-break logic. First check domain overlap - the same domains ranking, even on different pages, are closer than shared domains would suggest. Then intent: matching intent leans toward a shared cluster. Then volume: a 10x gap means the big keyword justifies its own post. When it stays ambiguous, err toward cohesion - same cluster, separate posts.
What the answers look like
Here is the full stdout from the run I described in the opening - ten keywords from a fresh US Google SERP capture, 89 organic URLs, 45 compare. I have trimmed nothing; the zeros are the point.
SERP overlap cluster scan serp.tsv
10 keywords / 45 pairwise comparisons
ubiquitous domains (7 of 10 SERPs): www.semrush.com (7)
overlap distribution:
seo audit x technical seo audit 5 shared (same cluster)
keyword research x keyword research tools 5 shared (same cluster)
seo tools x best seo tools 4 shared (same cluster)
seo audit checklist x technical seo audit checklist 3 shared (interlink)
technical seo x technical seo audit 1 shared (separate)
seo tools x seo checklist 1 shared (separate)
best seo tools x seo checklist 1 shared (separate)
seo audit x seo audit checklist 0 shared (separate)
seo audit x technical seo 0 shared (separate)
seo audit x seo tools 0 shared (separate)
seo audit x best seo tools 0 shared (separate)
seo audit x keyword research 0 shared (separate)
seo audit x keyword research tools 0 shared (separate)
seo audit x seo checklist 0 shared (separate)
seo audit x technical seo audit checklist 0 shared (separate)
seo audit checklist x technical seo 0 shared (separate)
seo audit checklist x technical seo audit 0 shared (separate)
seo audit checklist x seo tools 0 shared (separate)
seo audit checklist x best seo tools 0 shared (separate)
seo audit checklist x keyword research 0 shared (separate)
seo audit checklist x keyword research tools 0 shared (separate)
seo audit checklist x seo checklist 0 shared (separate)
technical seo x seo tools 0 shared (separate)
technical seo x best seo tools 0 shared (separate)
technical seo x keyword research 0 shared (separate)
technical seo x keyword research tools 0 shared (separate)
technical seo x seo checklist 0 shared (separate)
technical seo x technical seo audit checklist 0 shared (separate)
technical seo audit x seo tools 0 shared (separate)
technical seo audit x best seo tools 0 shared (separate)
technical seo audit x keyword research 0 shared (separate)
technical seo audit x keyword research tools 0 shared (separate)
technical seo audit x seo checklist 0 shared (separate)
technical seo audit x technical seo audit checklist 0 shared (separate)
seo tools x keyword research 0 shared (separate)
seo tools x keyword research tools 0 shared (separate)
seo tools x technical seo audit checklist 0 shared (separate)
best seo tools x keyword research 0 shared (separate)
best seo tools x keyword research tools 0 shared (separate)
best seo tools x technical seo audit checklist 0 shared (separate)
keyword research x seo checklist 0 shared (separate)
keyword research x technical seo audit checklist 0 shared (separate)
keyword research tools x seo checklist 0 shared (separate)
keyword research tools x technical seo audit checklist 0 shared (separate)
seo checklist x technical seo audit checklist 0 shared (separate)
verdict counts: same cluster=3, interlink=1, separate=41
wrote cluster-plan.md
group 1 (2 keywords, ~1 posts): seo audit, technical seo audit
group 2 (2 keywords, ~1 posts): seo tools, best seo tools
group 3 (2 keywords, ~1 posts): keyword research, keyword research tools
group 4 (1 keywords, ~1 posts): seo audit checklist
group 5 (1 keywords, ~1 posts): technical seo
group 6 (1 keywords, ~1 posts): seo checklist
group 7 (1 keywords, ~1 posts): technical seo audit checklistThree readings matter here.
The anti-pair. "Seo audit" versus "seo audit checklist" at 0 makes the case. The SERP for "seo audit" is tool homepages - SEOptimer, Semrush's Site Audit, Neil Patel's audit page. The SERP for "seo audit checklist" is tutorial posts - Notion's template, Moz's checklist, HubSpot's offer page, a Quora answer. Different content types, no shared competitors, no shared topics beyond the obvious. Same page would cannibalize; separate pages, and later links between them, match what Google is showing.
The surprise pair. "Technical seo" and "technical seo audit" at 1 is the opposite surprise. Text says these overlap ("technical seo" is a major term with a modifier), SERP says otherwise - "technical seo" brings up concept and explainer content while "technical seo audit" brings up checklists and the audit tools. You would have guessed these belong together and the measurement would have told you they do not.
The three clusters and the interlink. Audit-pair (4-5 results), research-pair, tools-pair. The checklist pair sits at 3 - interlink verdict - which is exactly the right call for adjacent content: "seo audit checklist" covers a site-wide audit, "technical seo audit checklist" zooms into the technical half. Two pages, distinct positions, cross-linked.
One data hygiene note: the script flagged semrush.com in 7 of 10 SERPs. In an SEO keyword set that is how the world is, not a defect - the flag is there so you notice when a genuinely ubiquitous domain (Wikipedia, Reddit, a marketplace) is carrying your overlap scores, and can check whether to weight or drop it before you build structure on the numbers.
Install the skill
Two files, same shape as the rest of this series:
mkdir -p ~/.codex/skills/codex-seo-cluster/scriptsSave this as ~/.codex/skills/codex-seo-cluster/SKILL.md:
---
name: codex-seo-cluster
description: Use when the user asks about topic clustering, content clustering, semantic clustering, keyword grouping, pillar pages, hub and spoke architecture, content architecture, cluster plans, related keyword planning, or cannibalization between pages. Groups keywords by which Google results they actually share (SERP overlap), not by text similarity, and outputs a hub-and-spoke cluster plan with an internal link matrix.
---
# Topic Clustering from SERP Overlap
Cluster keywords by how Google actually ranks them. Two keywords that share
the same top-10 organic results should target the same page; two keywords
with different results need separate pages. Google is the judge, not word
similarity, stemming, or related-search suggestions.
## 1. Expand the seed
30-50 candidate keywords, any mixture of:
- related searches and "people also search for" for the seed
- People Also Ask questions from the seed's SERP
- long-tail modifiers: best, how to, vs, for beginners, tools, examples,
guide, template, mistakes, checklist
- question stems: who/what/when/where/why/how + seed
- commercial modifiers: pricing, review, alternative, comparison, free, top
Normalize (lowercase, strip articles), deduplicate. Under 30 variants: run
a second pass seeded by the top PAA questions.
## 2. Collect SERPs
For each keyword, gather the organic results. Any of:
1. The orchestration agent's own web search (treat its result list as the
SERP; run each keyword once; note the counts per row).
2. `serp_google_organic_live` style API exports pasted into a file.
3. A file the user hands you (`serp.tsv`): `keyword<TAB>url` per row, or
`keyword,url1,url2,...` per row.
Use only organic URLs: drop ads, featured snippets, PAA, knowledge panels,
maps, and links outside the top 10. Normalize each URL: remove the protocol,
the trailing slash, and the query string; lowercase the domain.
## 3. Score the overlap
```bash
python3 ~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.py <serp.tsv>
python3 ~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.py <serp.tsv> --json
```
The script computes the shared-URL count for every keyword pair and applies
the thresholds:
| Shared results | Relationship | Action |
|----------------|-------------|--------|
| 7-10 | Same post | Merge into one target page; use the higher-volume keyword as primary |
| 4-6 | Same cluster | Same spoke cluster; separate posts if the volume gap is large |
| 2-3 | Interlink | Adjacent clusters; add cross-links between those posts |
| 0-1 | Separate | Different clusters, or exclude from this pillar |
**3-4 ties** (the boundary zone): fall back to domain overlap (same
domains, different pages = closer), intent alignment, and volume ratio
(one keyword at 10x+ volume probably earns its own post). When in doubt,
keep keywords in the same cluster with separate posts.
## 4. Classify intent
| Intent | Signals | Include? |
|--------|---------|----------|
| Informational | how, what, why, guide, tutorial, learn | Yes |
| Commercial | best, top, review, comparison, vs, alternative | Yes |
| Transactional | buy, price, discount, coupon, order, sign up | Yes |
| Navigational | brand names, specific products, login | No - drop from clustering |
Mixed intent: classify by the dominant intent; flag ambiguous ones for a
human review.
## 5. Hub-and-spoke architecture
1. **Pillar keyword**: highest volume, broadest intent, most overlap with
the other keywords.
2. **Clusters per pillar**: 2-5 subtopic areas; 2-4 posts per cluster;
5-21 posts total.
3. **Template by intent** (avoid duplicating templates inside a cluster
unless the SERPs justify it):
| Intent pattern | Template |
|----------------|----------|
| Informational broad | ultimate-guide |
| Informational how | how-to |
| Informational list | listicle |
| Informational concept | explainer |
| Commercial compare | comparison |
| Commercial evaluate | review |
| Commercial rank | best-of |
| Transactional | landing-page |
4. **Word counts**: pillar 2,500-4,000; spokes 1,200-1,800.
5. **Cannibalization**: no two posts share a primary keyword. If overlap is
7+, merge into one post targeting both. After planning, list all primary
keywords and scan for near-duplicates.
## 6. Link matrix
| Link type | Direction | Requirement |
|-----------|-----------|-------------|
| Spoke to pillar | spoke -> pillar | Mandatory, every spoke |
| Pillar to spoke | pillar -> spoke | Mandatory, every spoke |
| Same-cluster spoke | spoke <-> spoke | 2-3 per post, contextual anchors |
| Cross-cluster | spoke -> spoke | 0-1 per post, only with a real bridge |
Every post needs at least 3 incoming links; no orphan pages (everyone
reachable from the pillar in <= 2 hops). Anchor text = target keyword or a
close variant, never "click here"; place links in body copy. The script
writes `cluster-plan.md` with the group list, so expand it into the
full plan: per-post template, word count, URL, status, and the link list.
## 7. Output contract
Write `cluster-plan.md` with: seed keyword, date, pillar (title, keyword,
template, word count, URL, status), clusters (name + posts with the same
fields), the link list (from/to/type/anchor), the overlap matrix summary,
and a limitations paragraph naming anything you could not verify (missing
SERP data, volume estimates, weak overlap). State any step that was
performed with assumptions.
## Anti-patterns
1. Never cluster by text similarity alone: "seo audit" and "seo audit
checklist" can share zero results (tool SERPs vs tutorial SERPs).
2. Never group by stemming: "run" and "running" can target different intents.
3. Never assume related searches belong together; verify with overlap.
4. Never ignore SERP feature differences: a local pack or featured snippet
on one keyword and not the other argues for different content types.
5. Never treat all domains equally: Wikipedia and Reddit sit in many SERPs;
flag the most common domains (the script reports them) and judge whether
to weight or drop them.
## Cost and effort optimization
Full pairwise is N*(N-1)/2 comparisons (40 keywords = 780). Pre-group by
intent and head term, compare across groups only at boundary keywords, and
cache every SERP you fetch - reuse keyword A's results for every pair that
includes A. If an API is involved, run a cost check before each batch; if
the budget check says blocked, fall back to the agent's own web search.
## Errors
| Scenario | Action |
|----------|--------|
| File has fewer than 2 keywords | report DATA PROBLEM; ask for more keywords |
| Keyword with zero URLs | skip it and note the skip in the limitations paragraph |
| SERP data unavailable (search + API both down) | retry once after 30s, then intent-only clustering with an explicit warning |
| Overlap everywhere 0 | that is a real result, not a failure: the keywords are separate pages |
| Ubiquitous domains skew the matrix | quantify them, then rerun without them and compare verdicts |
| Conflicting verdicts after tie-break | prefer cohesion: same cluster, separate posts |
## Security
No credentials are stored or transmitted. The script reads only the local
SERP file. If you fetch URLs, use the agent's own fetch tooling - never raw
shell curl against user-supplied hosts.Now the script, as ~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.py:
#!/usr/bin/env python3
"""SERP-overlap clustering: decide content architecture from what Google
actually ranks. Takes one SERP file (keyword + top-10 organic URLs per
keyword) and returns the overlap matrix with threshold verdicts, cluster
groups (connected components at overlap >= 4), and merge warnings
(overlap >= 7 means one post). Standard library only. Usage:
python3 serp_cluster.py <serp.tsv> [--json]
Input file: one line per <keyword> TAB <url>, or one line per <keyword>
with comma-separated URLs after it. URLs are normalized (protocol,
trailing slash, query string removed) before comparison.
"""
import csv
import json
import sys
from collections import defaultdict
def url_path(url):
u = url.strip().split("#")[0].split("?")[0]
if "://" in u:
u = u.split("://", 1)[1]
return u.rstrip("/").lower()
def load(path):
"""Return {keyword: [normalized url paths]}, preserving order."""
data = defaultdict(list)
with open(path, newline="", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
tokens = line.split("\t")
if len(tokens) == 2:
kw, url = tokens
data[kw].append(url_path(url))
else:
parts = [p.strip() for p in line.split(",")]
kw = parts[0]
for p in parts[1:]:
if p:
data[kw].append(url_path(p))
return {k: list(dict.fromkeys(v)) for k, v in data.items()}
def main():
args = [a for a in sys.argv[1:] if not a.startswith("--")]
if not args:
print("serp_cluster.py - SERP overlap clustering (no API key needed)")
print(" python3 serp_cluster.py <serp.tsv> [--json]")
return
data = load(args[0])
kws = list(data)
if len(kws) < 2:
print("DATA PROBLEM: need at least 2 keywords with URLs")
return
# Ubiquitous domains: domains present in more than half of the SERPs are
# noise (Wikipedia, Reddit, ...); report them so the user can drop them.
per_domain = defaultdict(int)
for urls in data.values():
for u in urls:
per_domain[u.split("/", 1)[0]] += 1
n = len(kws)
common = sorted(
[(d, c) for d, c in per_domain.items() if c * 2 > n],
key=lambda t: -t[1])
# Pairwise overlap on normalized URL sets.
pairs = []
for i in range(len(kws)):
for j in range(i + 1, len(kws)):
a, b = data[kws[i]], data[kws[j]]
shared = sorted(set(a) & set(b), key=lambda x: (x, len(x)))
pairs.append((shared, i, j))
pairs.sort(key=lambda t: (-len(t[0]), t[1], t[2]))
def verdict(score):
if score >= 7:
return "same post"
if score >= 4:
return "same cluster"
if score >= 2:
return "interlink"
return "separate"
# Cluster groups: keywords are vertices; an edge is overlap >= 4. The
# connected components are the cluster suggestion.
parent = {k: k for k in kws}
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
for s, i, j in pairs:
if len(s) >= 4:
ra, rb = find(kws[i]), find(kws[j])
if ra != rb:
parent[ra] = rb
groups = defaultdict(list)
for k in kws:
groups[find(k)].append(k)
cluster_groups = sorted(
list(groups.values()), key=lambda g: -len(g))
counts = defaultdict(int)
for s, i, j in pairs:
counts[verdict(len(s))] += 1
merges = [p for p in pairs if len(p[0]) >= 7]
out = {
"file": args[0],
"keywords": len(kws),
"pairs": len(pairs),
"ubiquitous_domains": [{"domain": d, "serps": c} for d, c in common],
"pairs_by_verdict": dict(counts),
"merge_warnings": [
{"a": kws[i], "b": kws[j], "shared": len(s),
"shared_urls": s[:8]}
for s, i, j in merges],
"cluster_groups": [
{"keywords": g,
"posts_estimated": max(1, len(g) // 2)}
for g in cluster_groups],
}
if "--json" in sys.argv:
print(json.dumps(out, indent=2))
return
print("SERP overlap cluster scan %s" % args[0])
print(" %d keywords / %d pairwise comparisons"
% (len(kws), len(pairs)))
if common:
print(" ubiquitous domains (%s of %d SERPs): %s"
% (common[0][1], n, ", ".join(
"%s (%d)" % (d, c) for d, c in common)))
print(" overlap distribution:")
for s, i, j in pairs:
print(" %-32s x %-32s %2d shared (%s)"
% (kws[i][:32], kws[j][:32], len(s), verdict(len(s))))
print(" verdict counts: %s"
% ", ".join("%s=%d" % (v, counts[v])
for v in ("same post", "same cluster",
"interlink", "separate") if counts[v]))
out_dir = "cluster-plan.md"
with open(out_dir, "w", encoding="utf-8") as f:
f.write("# Cluster plan (generated by serp_cluster.py)\n\n")
f.write("- keywords: %d, pairs: %d\n\n" % (len(kws), len(pairs)))
f.write("## Suggested groups (overlap >= 4)\n\n")
for i, g in enumerate(cluster_groups):
f.write("%d. %s\n\n" % (i + 1, " / ".join(g)))
f.write("## Merge warnings (overlap >= 7 -> one post)\n\n")
if merges:
for s, i, j in merges:
f.write("- %s + %s (%d shared)\n"
% (kws[i], kws[j], len(s)))
else:
f.write("- none\n")
print(" wrote %s" % out_dir)
for i, g in enumerate(cluster_groups):
print(" group %d (%d keywords, ~%d posts): %s"
% (i + 1, len(g), max(1, len(g) // 2), ", ".join(g)))
if __name__ == "__main__":
main()Run it
cd ~/Desktop # or anywhere you keep working files
# one line per keyword-TAB-url; the example file that produced the run above:
python3 ~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.py serp.tsv
python3 ~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.py --json serp.tsv > matrix.jsonThe skill's self-check: pick 20-40 keywords, run the script, and confirm at least two verdicts appear across the pairs - and that any structured pair tells a story you can explain (my run's "seo audit" x "seo audit checklist" at 0, for instance). If every pair comes back "separate", your keywords are too unrelated - that is the signal to expand the seed, not a script bug. It is also completely fine for "same post" (7+) to never appear; that verdict means one fewer page to write, and absence of a merge is still a real finding.
To reproduce this exact run, save the SERP file below as serp.tsv (keyword, TAB, URL - one row per URL, organic results only, top 10 per keyword) and run the script. It prints the same 45 pairs, then writes cluster-plan.md:
# serp-10kw.tsv - the file that produced the run above (89 rows, 10 keywords, US desktop capture)
seo audit https://www.seoptimer.com/
seo audit https://www.semrush.com/siteaudit/
seo audit https://app.neilpatel.com/en/site-audit
seo audit https://seomator.com/free-seo-audit-tool
seo audit https://www.siteimprove.com/glossary/seo-website-audit/
seo audit https://www.ama.org/marketing-news/how-to-do-an-seo-audit/
seo audit https://seositecheckup.com/
seo audit https://rankmath.com/tools/seo-analyzer/?
seo audit checklist https://www.notion.com/templates/the-ultimate-seo-audit-checklist-for-2025?srsltid=AfmBOoqFvml_YBIAQ7npELhqt3UBuq-YcFXN3BNaAYHbtFpi7HBnurLM
seo audit checklist https://backlinko.com/tools/seo-checker
seo audit checklist https://moz.com/seo-audit-checklist
seo audit checklist https://shortlist.io/blog/technical-seo-audit-checklist/
seo audit checklist https://offers.hubspot.com/how-to-run-seo-audit
seo audit checklist https://www.wordstream.com/blog/ws/2022/01/24/website-audit-checklist
seo audit checklist https://serpstat.com/blog/seo-audit-guide/
seo audit checklist https://www.spyfu.com/blog/seo-audit/
seo audit checklist https://www.quora.com/How-do-I-make-a-perfect-SEO-Audit-Checklist
technical seo https://technicalseo.com/
technical seo https://www.semrush.com/blog/technical-seo/
technical seo https://developers.google.com/search/docs/fundamentals/get-started
technical seo https://www.mtu.edu/umc/services/websites/seo/technical-seo/
technical seo https://www.geeksforgeeks.org/blogs/what-is-technical-seo/
technical seo https://wpseoai.com/blog/what-is-technical-seo-with-10-examples/
technical seo https://nav43.com/blog/what-is-technical-seo/
technical seo https://ahrefs.com/seo/technical-seo
technical seo https://searchatlas.com/blog/technical-seo/
technical seo audit https://technicalseo.com/
technical seo audit https://www.semrush.com/siteaudit/
technical seo audit https://seomator.com/free-seo-audit-tool
technical seo audit https://app.neilpatel.com/en/site-audit
technical seo audit https://www.siteimprove.com/platform/seo/technical-seo-auditing-tools/
technical seo audit https://www.ama.org/marketing-news/how-to-do-an-seo-audit/
technical seo audit https://seositecheckup.com/
technical seo audit https://www.seobility.net/en/website-audit/
technical seo audit https://scandiweb.com/blog/seo-technical-audit-why-and-when-you-need-to-perform-it/
seo tools https://smallseotools.com/
seo tools https://technicalseo.com/tools/
seo tools https://moz.com/free-seo-tools
seo tools https://zapier.com/blog/best-seo-tools/
seo tools https://ahrefs.com/free-seo-tools
seo tools https://www.marketermilk.com/blog/best-seo-tools
seo tools https://www.youtube.com/watch?v=VOLpViuXh5U
seo tools https://www.techradar.com/news/best-seo-tool
seo tools https://morningscore.io/best-seo-tools/
best seo tools https://zapier.com/blog/best-seo-tools/
best seo tools https://technologyadvice.com/blog/sales/best-seo-tools/
best seo tools https://www.reddit.com/r/SEO/comments/1269zj7/whats_the_best_seo_tools_right_now_in_the_market/
best seo tools https://www.marketermilk.com/blog/best-seo-tools
best seo tools https://www.g2.com/categories/seo-tools
best seo tools https://www.visionefx.net/what-are-the-best-seo-tools/
best seo tools https://morningscore.io/best-seo-tools/
best seo tools https://userp.io/tools-and-software/top-seo-tools/
best seo tools https://www.youtube.com/watch?v=B58oCI0XVf0
keyword research https://www.semrush.com/analytics/keywordmagic/
keyword research https://business.google.com/us/ad-tools/keyword-planner/
keyword research https://backlinko.com/tools/keyword
keyword research https://zapier.com/blog/best-keyword-research-tool/
keyword research https://ahrefs.com/seo/keyword-research
keyword research https://cals.las.iastate.edu/post/basics-keyword-research
keyword research https://www.conductor.com/academy/keyword-research/
keyword research https://www.wordtracker.com/
keyword research https://keywordtool.io/
keyword research tools https://zapier.com/blog/best-keyword-research-tool/
keyword research tools https://www.semrush.com/analytics/keywordmagic/
keyword research tools https://business.google.com/us/ad-tools/keyword-planner/
keyword research tools https://app.neilpatel.com/en/ai-keyword-overview
keyword research tools https://www.reddit.com/r/Entrepreneur/comments/6vqxrn/heres_a_list_of_14_free_keyword_research_tools/
keyword research tools https://ahrefs.com/keyword-generator
keyword research tools https://keywordtool.io/
keyword research tools https://www.wordtracker.com/
keyword research tools https://community.hubspot.com/t/best-tools-for-keyword-research-for-seo-newbie/45408
seo checklist https://www.semrush.com/blog/seo-checklist/
seo checklist https://www.reddit.com/r/SEO/comments/rpvl7l/here_is_my_complete_seo_checklist_that_i_use_to/
seo checklist https://university.webflow.com/resources/seo-checklist
seo checklist https://ahrefs.com/blog/seo-checklist/
seo checklist https://backlinko.com/seo-checklist
seo checklist https://www.seoclarity.net/blog/seo-checklist
seo checklist https://www.marketveep.com/blog/your-monthly-seo-checklist-6-essential-tasks-for-success
seo checklist https://www.youtube.com/watch?v=vaxXNpxNWl0
seo checklist https://aioseo.com/seo-checklist/
technical seo audit checklist https://sitebulb.com/technical-seo/audit-checklist/
technical seo audit checklist https://www.semrush.com/blog/technical-seo-checklist/
technical seo audit checklist https://moz.com/seo-audit-checklist
technical seo audit checklist https://www.notion.com/templates/technical-seo-audit-checklist?srsltid=AfmBOoq5O3QhJFmp2c4c9pp04ScS4QU4R_uOxY6l0AAdY4R33iWCE2Vb
technical seo audit checklist https://shortlist.io/blog/technical-seo-audit-checklist/
technical seo audit checklist https://serpstat.com/blog/seo-audit-guide/
technical seo audit checklist https://www.pixelcrayons.com/blog/digital-marketing/seo-audit-checklist-to-use/
technical seo audit checklist https://dashthis.com/blog/technical-seo-checklist/
technical seo audit checklist https://wellows.com/blog/technical-seo-checklist-for-agencies/SERP overlap cluster scan serp-10kw.tsv
10 keywords / 45 pairwise comparisons
ubiquitous domains (7 of 10 SERPs): www.semrush.com (7)
... (45 pairs, same as above)
verdict counts: same cluster=3, interlink=1, separate=41
wrote cluster-plan.md
group 1 (2 keywords, ~1 posts): seo audit, technical seo audit
group 2 (2 keywords, ~1 posts): seo tools, best seo tools
group 3 (2 keywords, ~1 posts): keyword research, keyword research tools
group 4 (1 keywords, ~1 posts): seo audit checklist
group 5 (1 keywords, ~1 posts): technical seo
group 6 (1 keywords, ~1 posts): seo checklist
group 7 (1 keywords, ~1 posts): technical seo audit checklistThe groups are the cluster skeleton: three pairs that belong together plus four singles. The script deliberately stops at "what does the data say" - turning groups into pillars and spokes, picking templates, and wiring the link matrix is the SKILL.md's job, because those decisions need the intent classification and your own volume data.

The reference tables
Template selection by intent (one per post, no duplicates per cluster unless the SERPs justify them):
Intent pattern | Template |
|---|---|
Informational broad | ultimate-guide |
Informational how | how-to |
Informational list | listicle |
Informational concept | explainer |
Commercial compare | comparison |
Commercial evaluate | review |
Commercial rank | best-of |
Transactional | landing-page |
Word count and cluster shape:
Element | Target |
|---|---|
Pillar page | 2,500-4,000 words |
Spoke posts | 1,200-1,800 words |
Clusters per pillar | 2-5 |
Posts per cluster | 2-4 |
Total posts per pillar | 5-21 |
Link matrix requirements:
Link type | Direction | Requirement |
|---|---|---|
Spoke to pillar | spoke -> pillar | Mandatory, every spoke |
Pillar to spoke | pillar -> spoke | Mandatory, every spoke |
Same-cluster spokes | spoke <-> spoke | 2-3 per post, contextual anchors |
Cross-cluster | spoke -> spoke | 0-1 per post, real bridge only |
Incoming per post | - | At least 3 |
Orphan pages | - | 0 (every page reachable from pillar in <= 2 hops) |
Failure paths and honest edges
Two things the script will not do, on purpose:
It refuses to cluster garbage. A file with one keyword starts with DATA PROBLEM: need at least 2 keywords with URLs. A hand-typed SERP file where a keyword line has no URLs silently drops the row and the SKILL.md's output contract tells you to disclose the skip.
It treats zero overlap as a finding, not a bug. If two keywords come back with 0 shared results, that is the verdict. The temptation is to fudge the normalization or lower the threshold until the data agrees with your instinct - do not. The 5-shared pair in my run ("seo audit" x "technical seo audit") is not a strong merge signal, for example: the shared URLs are audit-tool and tutorial pages that Google ranks for both, while the volume gap between the two keywords argues for separate posts. That analysis belongs in the cluster plan's limitations paragraph, exactly where the skill tells you to put it.
Troubleshooting
Problem | Cause | Fix |
|---|---|---|
| Too few keywords in file | Expand the seed (SKILL.md section 1) before scoring |
All pairs "separate" | Keywords touch unrelated subtopics | Broaden the seed or split into two pillar plans |
3 shared everywhere | Over-normalized URLs or one dominant domain | Check |
7+ shared pairs you did not expect | The SERPs genuinely overlap | Trust it: merge posts, or re-verify by inspecting the shared URLs |
Missing keyword completely | The row was not in the file | Re-export the SERP file; note skipped keywords in the plan |
python3 not found | Codex without a Python install | Install python3; nothing else is required |
Wide columns wrap in terminal | Terminal width, not a data error | Use |
Paste this whole article into Codex
Copy the two code blocks above into Codex together with this paragraph:
Read the markdown block and the python block in the current message. Create~/.codex/skills/codex-seo-cluster/SKILL.mdand~/.codex/skills/codex-seo-cluster/scripts/serp_cluster.pyexactly as written. Then run the script with no arguments to confirm it is installed, and describe the two situations in which the skill refuses to produce a cluster verdict at all.
FAQ
Why not just cluster with a keyword tool's similarity grouping? Because similarity is measured on words, and Google ranks on its own criteria. A keyword tool that groups "best CRM" with "CRM comparison" because they are lexically close is guessing; the same pair may return entirely different results. Tools are good at the raw material - volumes, difficulty, pattern data - and weak as the clustering judge. Use them for expansion, use overlap for verdicts.
Do the search, SERPs and result counts need to be exact? No - and this is worth knowing before you over-engineer the data collection. One reliable set of organic results per keyword is enough to score overlap. Multiple snapshots per keyword are an upgrade, not a requirement. What does matter is that every keyword in the file comes from the same capture session and is normalized the same way; mixing markets or time periods contaminates the pair comparisons.
What if my seed keywords include brand terms? Drop them at intent classification, before scoring. Navigational keywords (branded, product-name, login) are the one category that is never useful inside a content cluster - they are destination queries, not informational ones. Keeping them inflates the pairwise count and muddies the verdicts.
Is this method useful for AI search, or is it classic SEO only? The method transfers. AI answers prefer to cite single pages that authoritatively cover a topic, and the same self-competition problem applies: two overlapping pages covering the same claim. Clustering by overlap keeps the citation surface clean - one page per question, strong pillar coverage linking the specifics - which is the same architecture AI systems reward when they assemble an answer.
How often should I re-cluster? When your content changes the landscape. Re-run the overlap check after a major launch, after new competitor pages start ranking consistently, or every six months as a baseline check. The scoring itself takes seconds; what takes time is collecting the SERPs, and only the keywords whose rankings you believe have moved need re-collection.
This says Google is the judge. What happens when Google is wrong? Then you note it and do the smarter thing anyway. The method's value is that it makes Google's boundary visible; the limitation is that Google's boundary is not always what your site needs. Where the verdict conflicts with your strategy, the skill's tie-break rules and the output contract's limitations paragraph exist to record the disagreement - and a cluster plan that documents the judgment call beats one that hides it.
Next in the series (post 13 of 20): [How to Set Up Codex for Hreflang Audits (Full SKILL.md Included)](https://auspia.ai/blog/codex-seo-hreflang) - multilingual and multi-region hreflang audits.
Previous in the series: How to Set Up Codex for Google Search Data (Full SKILL.md Included). The full series roadmap lists all 20 posts.
Author: David Sinclair, Topical Authority Strategist Across 500+ Topic Clusters at Auspia. David writes about topic clusters, content architecture, and coverage planning.
Based on the open-source claude-seo project by AgriciDaniel (MIT license, GitHub). This series adapts it for the Codex runtime: rewritten methodology, a Codex-native SKILL.md, and new evidence collectors written from scratch for this series.




