Two keywords that return the same Google results should be targeted by the same page. Two keywords that return completely different results need separate pages. This is the foundation of SERP-based clustering -- using Google's own ranking decisions to determine content architecture rather than relying on keyword text similarity or stemming.
For each keyword in the candidate set, retrieve the top 10 organic results:
- Use WebSearch or DataForSEO serp_organic_live_advanced
- Extract only organic result URLs (ignore ads, featured snippets, PAA, knowledge panels)
- Normalize URLs: strip protocol, trailing slash, and query parameters (except meaningful ones)
- Store as a set of 10 URLs per keyword
For each pair of keywords (A, B):
1. Retrieve the URL sets: urls_A and urls_B
2. Compute overlap: shared = urls_A intersection urls_B
3. Score: overlap_score = len(shared)
| Overlap Score | Relationship | Action |
|---|---|---|
| 7-10 | Same post | Merge keywords into one target page. Use higher-volume keyword as primary. |
| 4-6 | Same cluster | Place in same spoke cluster. May be separate posts or same post depending on volume difference. |
| 2-3 | Interlink | Place in adjacent clusters. Create cross-cluster internal links. |
| 0-1 | Separate | Different clusters entirely or exclude from current pillar topic. |
Scores in the 3-4 range require tiebreaking: 1. Check domain overlap (same domains but different pages = closer relationship) 2. Check intent alignment (same intent category = lean toward same cluster) 3. Check volume ratio (if one keyword has 10x+ more volume, it likely deserves its own post) 4. When in doubt, keep in same cluster with separate posts (err toward cohesion)
Full pairwise comparison of N keywords requires N*(N-1)/2 SERP fetches. For 40 keywords, that is 780 comparisons. Optimize by reducing unnecessary checks:
Store the overlap data as a symmetric matrix in cluster-plan.json:
{
"serp_matrix": {
"keywords": ["keyword-a", "keyword-b", "keyword-c"],
"scores": [
[10, 5, 1],
[5, 10, 3],
[1, 3, 10]
]
}
}
Diagonal is always 10 (a keyword overlaps perfectly with itself).
serp_organic_live_advanced with location_code: 2840 (US) and language_code: "en".Within a single clustering session, cache all SERP results. If keyword A's results are fetched for the A-B comparison, reuse them for the A-C comparison. This halves the number of actual SERP fetches needed.