Open Search Console, pick a query you care about, and look at the Pages tab. If two of your URLs have both been collecting impressions for that query, you are looking at cannibalization — not the dramatic version, where a penalty wipes out traffic, but the ordinary version, where two pages take turns in the result and neither gets the full benefit.
It is common on WordPress sites because publishing is cheap. A new post about the same topic, a landing page for a keyword, a category that happens to match a query, an old draft that was never redirected. Each one looks reasonable on its own. Together they make Google choose.
What Keyword Cannibalization Is, and What It Is Not
The working definition: two or more URLs on the same site are eligible for the same query, and Google alternates which one it shows. The pages are not duplicates in the technical sense. They may have different titles, different examples, and different lengths. What they share is the intent they target.
That distinction matters because the fixes are different. Duplicate content — the same page reachable at several URLs — is a canonicalization problem. Cannibalization is an editorial and structural problem: two pages are trying to answer one question.
It is not a penalty. There is no manual action for it, and you will not find it in the Manual Actions report. It is not a bug in Google, either. Google picks a URL for each result, and when two pages look similarly relevant, the choice can change with the query, the device, or the week.
What it does cost:
- Clicks split across URLs. Two pages at positions 4 and 9 earn less combined traffic than one page at 4, because the second result is competing with the first for the same click.
- Unstable rankings. If the URL changes every few weeks, so does the click-through rate, and you cannot tell whether a change helped.
- Wasted internal links. Half your links point at page A, half at page B, and neither gets the full signal.
- Wasted crawl and maintenance. Two pages to update, two pages to fact-check, two pages to keep from drifting apart.
Why Google Swaps the URL Every Few Weeks
Google’s canonicalization documentation describes the mechanism: when several URLs are similar or duplicate, Google consolidates “the signals they have for the individual URLs (such as links to them) into a single, preferred URL.” That consolidation only happens cleanly when Google agrees the pages are versions of the same thing.
Two pages targeting the same query but built around different content are not versions of the same thing. Google treats them as separate results competing for one slot. It may prefer one for a while, then switch as the pages change or as Google re-evaluates them. The ranking graph looks volatile; the cause is structural.
There is a second, quieter symptom. Check the query’s average position over time. If it oscillates by several places while the pages themselves have not changed, the two URLs may be trading places. The average position is the average of the topmost result, so an alternation between position 5 and position 12 shows up as a number that never settles.
Find the Overlap in Search Console
The Performance report is enough for the first pass. Set the date range to three months, open the Queries tab, and sort by impressions.
- Click a query you care about.
- Switch to the Pages tab. The query filter stays applied.
- Count the URLs with meaningful impressions.
Two URLs on one query is a suspect. Three is a pattern. The threshold is yours to set; a page with two impressions is noise, not evidence. Start with pages above 50 impressions over the quarter, then lower it for a small site.
The reverse direction works too. Open the Pages tab, click a page, then switch to Queries. You will see every query that page appeared for. Do it for two pages you suspect are related, and compare the lists. The overlap is your shortlist.
For a site with a few hundred posts, this is an afternoon of clicking. For a site with thousands, export the data or use the Search Console API. The API accepts ["query", "page"] as dimensions, which gives you every query-page pair in one response. rowLimit goes up to 25,000 and defaults to 1,000; startRow pages through the rest.
The Script That Ranks Your Suspects
This reads the Search Console API, groups rows by query, and prints the queries where more than one page cleared an impression threshold. It is read-only, and it assumes a token.json from an OAuth client with the Search Console API enabled.
from collections import defaultdict
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
SITE = "sc-domain:wpfrank.com" # or https://wpfrank.com/
CREDS = "token.json" # webmasters.readonly scope
MIN_IMPRESSIONS = 50 # per page, per query
creds = Credentials.from_authorized_user_file(
CREDS, ["https://www.googleapis.com/auth/webmasters.readonly"]
)
service = build("searchconsole", "v1", credentials=creds)
rows, start = [], 0
while True:
body = {
"startDate": "2026-08-01",
"endDate": "2026-10-31",
"dimensions": ["query", "page"],
"rowLimit": 25000,
"startRow": start,
"dataState": "final",
}
batch = service.searchanalytics().query(
siteUrl=SITE, body=body
).execute().get("rows", [])
rows.extend(batch)
if len(batch) < 25000:
break
start += 25000
by_query = defaultdict(list)
for row in rows:
query, page = row["keys"]
by_query[query].append((
page,
row.get("impressions", 0),
row.get("clicks", 0),
row.get("position", 0),
))
suspects = []
for query, pages in by_query.items():
strong = [p for p in pages if p[1] >= MIN_IMPRESSIONS]
if len(strong) > 1:
suspects.append((
query,
len(strong),
sum(p[1] for p in strong),
sum(p[2] for p in strong),
strong,
))
suspects.sort(key=lambda s: s[2], reverse=True)
for query, count, impressions, clicks, pages in suspects[:50]:
print(f"\n{query} | {count} pages | {impressions} impr | {clicks} clicks")
for page, impr, clk, pos in sorted(pages, key=lambda p: -p[1])[:4]:
print(f" {impr:>6} impr {clk:>4} clicks pos {pos:5.1f} {page}")
Run it once a quarter. The output is not a verdict; it is a list of queries to inspect by hand. Some rows will be false positives, and the next section is about telling them apart.
Five Patterns That Only Look Like Cannibalization
1. The same words, different intent. “WordPress caching” and “WordPress object caching” share a word, not a searcher. One wants a plugin recommendation, the other wants a technical explanation. If the pages answer different questions, they can both rank. The test is the search result: if you would be annoyed to land on page B when you searched for page A’s topic, they serve different intents.
2. A category and a post. A category archive titled “SEO” and a post titled “SEO” will overlap because the archive’s title matches the query. That is a structural problem, not an editorial one: the category should link to the post and target a broader term, or the post should be the only page optimized for the query. Our guide to structuring WordPress pages for SEO covers how to assign one job per URL, and the SEO basics for developers explain why a URL’s title is a promise about intent.
3. Location pages. “Plumber in Leeds” and “Plumber in Bradford” will share the word “plumber” and split generic impressions. That is expected. They become cannibalization only when both pages target the same city with near-identical copy.
4. Product variants. A variable product can expose several URLs for the same item. Google’s canonical documentation is the right tool here: pick the parent or the variant with demand, and consolidate. This is duplicate content, not an editorial overlap.
There is a fifth case worth naming because it is not a problem at all: the homepage and a landing page both ranking for a brand query. Google chooses between them, and the homepage usually wins. Unless the landing page is the one you want in results, leave it.
Merge, Canonicalize, or Differentiate
Three fixes, and the choice depends on whether the pages deserve to exist separately.
| Situation | Fix |
|---|---|
| Same intent, overlapping content, one page is clearly stronger | Merge the unique parts into the stronger URL, then 301 the other to it. |
| Both URLs must exist and the content is near-identical (variants, print views, tracking parameters) | Canonicalize to the preferred URL with rel="canonical". |
| Same words, genuinely different intent | Differentiate: rewrite the title, H1, opening section, and internal anchor text so each page targets its own query. |
| Thin archive or tag page that adds nothing | Noindex only if you truly do not want it indexed. Not as a canonical tool. |
Differentiation is the option most people skip, because it takes writing rather than a redirect. It is also the right answer when both pages have earned links and serve different questions. Google’s people-first content guidance is the tie-breaker: if neither page would satisfy a reader on its own, merge them; if each answers a different question well, keep both and separate the targeting. Separating two intents is a semantic job, and our guide to semantic SEO in WordPress covers how titles, entities, and schema reinforce the distinction.
The last row needs a warning attached. Google’s canonicalization guidance is explicit: “We don’t recommend using noindex to prevent selection of a canonical page within a single site, because it will completely block the page from Search. rel="canonical" link annotations are the preferred solution.”
That sentence rules out the most popular shortcut. Adding noindex to the weaker page does not consolidate its signals into the stronger one; it removes the page and whatever links it had earned. If the weaker page has backlinks, a merge and redirect preserves more than a noindex ever will.
For a true merge, the 301 is the stronger signal. Google’s redirect documentation says certain redirect types are a signal that the target should be canonical, and permanent redirects are the clearest case. A 301 also sends visitors to the surviving page, which a canonical tag does not.
The WordPress Cleanup
The redirect itself is one line. In .htaccess, before the WordPress block:
Redirect 301 /old-page/ /new-page/
If you prefer a plugin, Redirection handles the same job and logs 404s, which is useful during the cleanup. Whichever plugin you use for canonicals — our roundup of free WordPress SEO plugins covers the options — check the rendered <head> rather than the toggle. If you are configuring one for the first time, the DefiniteSEO beginner guide walks through the canonical settings.
In every case, test the redirect with curl -I and confirm a single 301 to the final URL, not a chain:
curl -I https://wpfrank.com/old-page/
Then fix the links. A redirect catches external traffic, but internal links should point at the destination directly — Google’s guidance is to link to the canonical URL rather than a duplicate. Start with the navigation, then the posts that link to the old URL. WP-CLI can find them:
wp search-replace 'https://wpfrank.com/old-page/' 'https://wpfrank.com/new-page/' --dry-run
Run the dry run first and read the table counts. If the numbers look right, run it again without --dry-run. WP-CLI handles serialized data, which a raw SQL UPDATE does not.
Two housekeeping items:
- Sitemap: remove the old URL and confirm the surviving one is listed.
- Canonical tag: the surviving page should point at itself. A self-referential canonical is a documented best practice, and it prevents a plugin setting from pointing the winner at the page you just retired.
Do not stack a canonical tag and a 301 on the same old URL and call it belt-and-braces. The redirect is what visitors and crawlers follow; the canonical tag on a redirected URL is noise.
Verifying the Fix Without Panic
Use URL Inspection on the old URL. It should report “Page with redirect,” not “Duplicate” or “Alternate page.” Request indexing for the target if it has changed substantially.
Then wait, and watch three things in Search Console:
- Performance → Queries → Pages. The number of URLs for the query should drop to one. Clicks may dip for a week or two before they recover, because Google has to recrawl and re-evaluate.
- Page Indexing. The old URL should move to “Page with redirect.” A 404 here means the redirect is wrong or missing.
- Internal links. Spot-check the pages that used to link to the old URL. If the redirect is doing all the work, fix the links.
There is no fixed recovery timeline, and anyone who gives you one is guessing. What you can control is the consistency: one URL in the sitemap, one URL in the internal links, one canonical, one redirect. Cannibalization is one item on a longer list; our guide to boosting your SEO ranking covers the rest.
FAQ
Is keyword cannibalization a penalty?
No. It is not a manual action and it does not appear in the Manual Actions report. It is a competition problem inside your own site: two pages target one query, and Google has to choose between them.
How do I know if two pages are cannibalizing each other?
In Search Console, filter Performance to one query and open the Pages tab. If two URLs have meaningful impressions for that query, they are competing. Confirm by checking that the pages target the same intent, not just the same words.
Should I noindex the weaker page?
Generally no. Google advises against using noindex to manage canonical selection within a site because it removes the page from Search entirely, including any links it earned. Merge and redirect, or differentiate the pages instead.
Can I use a canonical tag instead of a redirect?
Yes, when both URLs need to stay live and the content is near-duplicate, such as product variants or tracking parameters. For a true merge, a 301 is cleaner: it sends a strong canonical signal and gets visitors to the surviving page.
Does cannibalization happen with categories and tags?
Yes, frequently. A category archive and a post can target the same phrase, and so can a tag page and a topic hub. Give the archive a broader job, link it to the post, and keep one page optimized for the query.
How long does it take to recover after merging pages?
It depends on crawl frequency and how much the pages differed. Watch the query in Performance for several weeks. A short dip is normal; a permanent drop usually means the surviving page lost content or links in the merge.
Can internal linking cause cannibalization?
It can reinforce it. If half your internal links point at page A and half at page B for the same topic, you are telling Google both matter. Point them at the page you want to rank, and the overlap becomes easier for Google to resolve.
Leave a Reply