Search Console, GA4, and your CMS (WordPress here, since that’s what we run) are usually three separate places to check: three logins, three exports, three URL formats that don’t match by default. Joined into one table, keyed on the same URL, they stop being three exports and become a single queryable dataset, a real dashboard you can ask questions of.
Two examples of what that makes visible: pages competing with each other for the same queries, and updates whose results get judged before Google has even had a chance to see them.
Both patterns show up on any site that’s had more than one person touching content over time. Strategy shifts, priorities change, different people make different calls without a shared view of what’s already live, that’s what causes them, not a one-off mistake.
I check for both with the cannibalization module in an internal dashboard built around a process I ran by hand for years before it existed: pulling Search Console, GA4, and WordPress data separately and merging it myself in spreadsheets. The dashboard covers a lot more than these two problems, editorial priorities, opportunity scoring, page-level performance, but here’s how I use it for these two specifically. Making a call between two competing pages takes three sources, each doing a different job:
- Search Console shows which one Google already prefers, and when it last crawled either.
- WordPress shows when each was last edited, so you know whether a before/after read is even trustworthy yet.
- GA4 shows the engagement rate for each, telling me whether the page I’m keeping needs anything from the one I’m redirecting.
Here’s a pair that came up when I ran this check.
The pair I found
Eight shared queries. One page pulling 38% of its own impressions from that shared set, the other 100%.
URLs and queries below are relabeled, real pages, real numbers, just not ones I’m naming publicly:
- 38.0% of Page A’s impressions come from these 8 queries
- 100.0% of Page B’s impressions come from these 8 queries
| Query | Page A impr. | Page A pos. | Page B impr. | Page B pos. |
|---|---|---|---|---|
| Query 1 | 668 | 3.0 | 44 | 3.0 |
| Query 2 | 26 | 26.0 | 46 | 6.2 |
| Query 3 | 69 | 7.9 | 1 | 13.0 |
| Query 4 | 35 | 6.7 | 33 | 9.6 |
| Query 5 | 17 | 8.0 | 2 | 9.5 |
| Query 6 | 11 | 7.2 | 7 | 10.7 |
| Query 7 | 8 | 9.0 | 1 | 7.0 |
| Query 8 | 4 | 77.8 | 3 | 88.3 |
Keep Page A. It averages position 4.8 on the shared queries versus 8.1 for Page B, Google already prefers it. Page B should be merged into Page A and redirected, it draws no impressions from outside this overlap.
Unique to Page A: 1,367 impressions outside the shared set. Unique to Page B: none of significant volume.
Page B has no traffic outside the shared queries, and Page A outranks it on every one of them. That’s not visible from either page’s Search Console numbers alone, only when you check what percentage of each page’s traffic comes from the shared set, a percentage Search Console doesn’t calculate for you.
Contrast this with a pair where the overlap is real but partial, say 40% for one page and 20% for the other. Redirecting there is riskier: both pages draw meaningful traffic the other doesn’t touch, so consolidating would throw away real, unique impressions. Same range of shared queries, a different correct action, and you can’t tell which situation you’re in without the percentage.
Before I redirect anything, I check GA4
Search Console only measures visibility, not what happens after the click. If the losing page has better engagement metrics, the fix is updating the winner’s format to match it, not just redirecting.
So before I acted on the recommendation above, I pulled GA4 engagement data for both pages over the same window:
| Metric (last 3 months) | Page A | Page B |
|---|---|---|
| Sessions | 53 | 10 |
| Engagement rate | 47.2% | 50.0% |
| Avg. engagement time | 31s | 22s |
Nothing here changes how Page A ends up looking. Page B’s engagement rate edges out Page A’s by under 3 points, but that’s on a sample of 10 sessions, too small to treat as a real difference. Its average engagement time is actually shorter, not longer. If Page B had real volume behind that engagement rate, or if its engagement time were ahead too, that’s the pattern that would send me back into Page A’s content to borrow whatever Page B was doing right, before touching the redirect. Here, there’s nothing worth carrying over.
That’s the typical outcome: most of the time, GA4 doesn’t turn up anything worth changing about the page I’m keeping. I run the check every time regardless, because the times it does are exactly the times I’d want to update that page’s format before redirecting, not just after.
Why I don’t trust a before/after check without a crawl date
The common way to measure a content update: change the page, note the date, come back in a couple weeks, compare Search Console numbers before and after. It’s also an easy way to reach the wrong conclusion.
Search Console’s Performance data doesn’t record when Google recrawled a page, only when clicks and impressions were logged. You can get a page’s last crawl date from the URL Inspection tool in the GSC UI, but only one URL at a time, and there’s no way to export it, you’d have to check and note down each URL by hand. Confirming it at any scale means the URL Inspection API instead.
So before trusting a before/after comparison, I check whether Google has recrawled the page since I edited it. If not, I stop. The dashboard also flags this automatically: any edit inside the last 14 days is too fresh to read, on top of Search Console’s 2-3 day reporting delay and GA4 dropping the current day as partial.
New content needs the same check, in reverse. The data point is when the new page’s URL first shows impressions in Search Console, not the publish date. Zero impressions for the first couple of weeks may just mean it’s not indexed yet, not that it’s underperforming.
What I check, every time
On every page pair, whether or not anything looks off:
- Any pair where one page draws 80%+ of its own impressions from queries the other page already outranks it on gets flagged for a merge or redirect decision.
- Position gap gets checked on the shared queries specifically, not each page’s overall average position, which is diluted by every unrelated query it also ranks for.
- Before acting on that flag, I pull GA4 engagement rate and average engagement time for both pages. If the “losing” page is ahead on both, with real session volume behind it, I update the winner’s format to match it before redirecting.
- Pairs with real, meaningful traffic unique to both pages get a closer look, not a redirect: I check whether the overlapping queries are actually important to each page on its own terms, and how much of each page’s unique impressions they’re pulling in, before deciding whether the two need to be differentiated or can stay as they are.
How I ask for it instead of exporting it
Once this is one connected dataset, I ask directly instead of exporting three things and eyeballing them:
Which of our page pairs have one page drawing 80%+ of its own impressions from non-branded queries it shares with another page, each query with a minimum of 30 impressions over the last 30 days? Doesn't matter if that's over 3 shared queries or 30.
Pull GA4 engagement rate, average engagement time, and sessions for [url] and [url]. Flag it if either is ahead on both engagement metrics.
Has Google recrawled [url] since I last edited it? If not, don't compare before/after numbers yet, tell me when it's safe to check.
Give me our top 10 cannibalized page pairs, ranked by overlap percentage. For each, show how much traffic each page gets, last crawl date, publish date, and engagement metrics for both.
These are ordinary questions to ask once the sources are connected. What made them hard before was three separate logins and three incompatible URL formats to reconcile before the analysis could even start.
🚀 See what your own Search Console, GA4, and WordPress data looks like connected in one place. Free forever plan, no credit card. Try Windsor.ai.
What I’m doing about this pair
Before redirecting, I check whether Page B has any content Page A doesn’t, something worth merging in rather than losing. Here it doesn’t: everything Page B ranks for, Page A already ranks for too, and outranks it on average. Page A keeps 1,367 impressions from outside the overlap. Page B’s contribution outside the overlap rounds to nothing, nothing meaningful to lose by redirecting it in.
Conclusion
Cannibalization and measuring too early are both common problems. Both stay hidden unless you’re looking at Search Console, GA4, and WordPress together.
I’ve built this kind of dashboard before, more than once, at previous jobs. Every time, step one was authenticating each source myself, a few minutes per connector, and it doesn’t always go smoothly. What’s different about doing it through a connector is what happens after that: I can share the dashboard, or a Claude skill built on top of it, with a teammate without handing them an API key, or with anyone who already has the same connectors set up on their own end.
The tools behind this
I pull this from our own Google Search Console, Google Analytics 4, and WordPress connectors. If you also want this queryable from Claude, here’s the Windsor MCP setup guide.
FAQs
Why check GA4 if Search Console already shows which page is winning?
Because Search Console only shows visibility, not what happens after the click. GA4’s engagement rate and average engagement time fill that gap: a page can be losing badly on visibility while still having better engagement metrics. If that’s true, the fix is updating the winning page’s format to match before redirecting, not skipping the format update.
How do I decide whether to redirect a cannibalized page, or leave both live?
Check what’s unique to each page outside the shared queries, and check GA4 engagement for both. If one page has close to zero unique traffic and no engagement edge, redirecting costs almost nothing. If both carry real unique traffic, look at the overlapping queries themselves, whether they matter to each page on its own terms and how much of each page’s unique impressions they’re actually pulling in, before deciding to differentiate the two or leave them as is.
Can I get the exact date Google recrawled a specific page?
Yes, from the URL Inspection tool in the GSC UI, but only one URL at a time, with no way to export it. Search Console’s standard Performance data doesn’t have it at all, only sitemap-level timestamps. Checking many URLs means the URL Inspection API instead.
Why don't Search Console, GA4, and WordPress URLs match automatically?
Search Console stores full URLs, GA4 stores paths, WordPress stores permalinks. Query strings, trailing slashes, and casing differ across all three, so every row needs to be normalized to the same key before anything joins correctly.
Do I need a custom tool to do this?
No. Connecting Search Console, GA4, and WordPress through Windsor.ai gives you the same joined, normalized data without building or maintaining the integration yourself, and once connected, you can ask Claude or ChatGPT about the blended data directly in plain language.
Windsor vs Coupler.io

