Detecting SEO keyword cannibalization between your pages with n8n and Google Search Console
Published 29 August 2026 · 6 min read
Two posts on the same blog, published months apart, that end up answering the exact same query. Search Console shows both in the performance report, each picking up a slice of impressions, neither one breaking past page three of results consistently. That's the classic symptom of SEO cannibalization: instead of concentrating the site's authority on one strong page, two URLs cancel each other out on the same search intent. The problem isn't visible at a glance in the Search Console interface — you have to cross-reference query and page over several weeks to catch it, exactly the kind of task an n8n workflow runs in seconds where a manual review takes a full morning per tracked site.
Why cannibalization actually hurts rankings
A search engine isn't trying to stack multiple pages from the same site for a given query — it's trying to cover the diversity of intents behind that query. Santos, Macdonald, and Ounis (2011), Intent-Aware Search Result Diversification, published at SIGIR, formalizes this principle: a search engine spreads its ranking to cover as many distinct intents as possible rather than piling up redundant results on the same intent. When two pages from the same site cover the same intent, neither adds any diversity relative to the other — the engine has no reason to surface both, and arbitrates by alternating between them, which shows up exactly as the position instability you see in Search Console.
This arbitration mechanism connects to an older, more thoroughly studied problem: near-identical content. Manku, Jain, and Das Sarma (2007), Detecting Near-Duplicates for Web Crawling, published at WWW and now a reference on large-scale similarity detection (the SimHash algorithm it introduced is still in use today), shows that search engines apply explicit mechanisms to catch documents whose content overlaps heavily, precisely to avoid saturating a results page with variants of the same content. Two pages targeting the same query with similar content fall into that same net — without necessarily being duplicates in the strict sense, intent overlap alone is enough to trigger the same kind of arbitration.
What Search Console shows — and what it doesn't show directly
The Search Console performance report, in its interface, shows queries and pages either separately or combined, but never explicitly flags that a query is being contested by several pages of your own site — that cross-reference is left for you to build. The searchanalytics.query API, on the other hand, exposes exactly the data needed as soon as you query both the query and page dimensions together: each returned row corresponds to a unique query-page pair, with its own clicks, impressions, CTR, and average position. That granularity is what lets you reconstruct, query by query, the list of pages competing for it.
Building the detector in n8n
1. Pull the data grouped by query and by page
A weekly Schedule Trigger fires an HTTP Request node in POST to https://www.googleapis.com/webmasters/v3/sites/{encoded siteUrl}/searchAnalytics/query, authenticated with a Google OAuth2 API credential using the webmasters.readonly scope — the same setup described in our guide to the weekly SEO report with Search Console. The request body:
{
"startDate": "2026-08-01",
"endDate": "2026-08-28",
"dimensions": ["query", "page"],
"rowLimit": 5000
}
A 28-day window smooths out daily noise without erasing real trends — a shorter window surfaces too many false positives on low-volume queries.
2. Group the rows by query in a Code node
const byQuery = {};
for (const item of $input.all()) {
const { keys, clicks, impressions, position } = item.json;
const [query, page] = keys;
byQuery[query] ??= [];
byQuery[query].push({ page, clicks, impressions, position });
}
const candidates = Object.entries(byQuery)
.filter(([, pages]) => pages.length >= 2)
.map(([query, pages]) => ({ query, pages: pages.sort((a, b) => a.position - b.position) }));
return candidates.map((c) => ({ json: c }));
At this stage, candidates contains every query where at least two pages on the site generated impressions — far too broad to be a useful alert as it stands, which matches the empirical observation that most cases of multiple rankings on the same site are actually harmless.
3. Filter down to real cannibalization cases
The filter that separates signal from noise rests on two combined criteria, applied in the same Code node or in an IF node right after:
- Tight position gap: the two top-ranked pages for the query have a position gap under 10 — two pages at spots 4 and 6 are genuinely competing for the same ground; a page at spot 3 and another at spot 47 aren't cannibalizing each other, the second one is just a marginal, ignorable match.
- Meaningful impression split: each page clears a minimum floor (say, 20 impressions over the period) — this filters out anecdotal queries where the second page was only seen a handful of times by chance.
For an even more reliable signal, compare two consecutive 28-day windows: if the leading page for the same query changes from one period to the next, that's the clearest signature of active cannibalization rather than stable coexistence.
4. Diagnose with an AI Agent before alerting
An AI Agent receives the URLs of the competing pages, their title and meta description (pulled via a simple HTTP Request node on each page), and returns a structured diagnosis using a Structured Output Parser: genuinely identical topic that should be merged, distinct intents that need clearer differentiation, or an ambiguous case requiring manual review. This diagnosis avoids dumping a raw list of URL pairs on the editorial team with no actionable context.
5. Alert without acting automatically
The result goes out as a Slack message or email — never as an automatic action. Merging two pages or setting up a 301 redirect permanently changes the site's structure; that's an editorial decision, not a task to fully delegate to a workflow. The Schedule Trigger with timezone handling lets you schedule this weekly alert for a Monday morning, alongside the usual SEO review.
Real-world use cases
- High-volume blog with regular publishing. Past 200-300 articles, it's common for a new topic to partially overlap with a post published a year earlier without the editorial team even remembering it — the detector surfaces these overlaps before they become entrenched.
- E-commerce site with close category and product pages. A generic category and a very similar subcategory sometimes end up targeting the same commercial query without meaning to.
- Agencies managing several client sites. The same workflow, parameterized with a Loop Over Items node over a list of Search Console properties, produces a consolidated multi-site report without duplicating the logic.
Common pitfalls
- Alerting on every multi-page query without filtering on position gap: most cases of multiple rankings are benign, and flooding the editorial team with false positives kills the alert's usefulness within a few weeks.
- Comparing too short a window (7 days): position noise on low-volume queries produces false signals that a 28-day window almost entirely eliminates.
- Merging pages automatically with no human review: the decision to merge two pages affects the site's architecture and existing internal linking; the workflow should stop at the diagnosis stage.
- Forgetting to check the
pagedimension: a query pulled without thepagedimension only returns an aggregated total and completely hides the phenomenon — it's thequery+pagecombination that makes cannibalization visible.
Going further
This detector naturally complements the weekly Search Console SEO report and tracking traffic decay per page: all three workflows query the same API with the same OAuth2 credential, and can run in the same n8n folder as one coherent SEO audit suite. This pattern — structured data collection, AI diagnosis, and a delivered report with no automatic action — is exactly what the Compliance & Audit Pack (€149) is built for: turning raw data into an actionable report while keeping human validation exactly where it belongs.
FAQ
Frequently asked questions
If two pages show up for the same query, is that always cannibalization?
No. Google regularly surfaces two pages from the same site for a broad query without any real problem: a product page and a blog post covering the same theme from a different angle can legitimately coexist if one clearly dominates the ranking and the other stays far behind. Problematic cannibalization shows a specific signature: the two pages swap positions week over week, or split comparable impressions without either one ever clearly winning out — it's that unstable split you need to detect, not mere coexistence.
Do you need a dedicated n8n node to query Search Console?
No, the Search Console API has no native node in n8n: it's driven with the HTTP Request node, authenticated via a Google OAuth2 API credential with the webmasters.readonly scope — the same setup used for a standard SEO report.
How often should this check run?
A weekly run is enough for most sites: cannibalization builds up gradually, over several weeks of publishing, not overnight. Since Search Console data lags by 2-3 days, a Monday-morning pass over the last 28 days gives a wide enough window to separate a real trend from one-off noise.
Once cannibalization is detected, what's the fix?
Three options depending on the case: merge the two pages into one if they genuinely cover the same topic (with a 301 redirect from the weaker one to the stronger one); clearly differentiate them if they answer distinct intents that drifted closer together over time (titles, angles, target keywords); or add a rel=canonical if one of the two was never meant to be indexed separately. The right call depends on the actual content of both pages, not just the Search Console numbers — which is why the diagnosis should go through a human before anyone acts on it.
Bundle FlowKit Complet
€269