Competitive Analysis strategy library: pillar + 4 plays (#237)
This commit is contained in:
parent
cd6a782092
commit
56826f84d1
115
web/content/marketing/library/backlink-gap-analysis.mdx
Normal file
115
web/content/marketing/library/backlink-gap-analysis.mdx
Normal file
@ -0,0 +1,115 @@
|
||||
---
|
||||
title: "Read a competitor's link profile before you copy it"
|
||||
description: "A big backlink number usually means a small number of domains linking many times. How to read referring domains, spam score, and broken links, and find the gap that is actually worth chasing."
|
||||
---
|
||||
|
||||
## Backlinks are the wrong unit
|
||||
|
||||
Pull the link profile of an established competitor and the first number you see is the largest one. Here is a real profile from a national brand in the property restoration category.
|
||||
|
||||

|
||||
|
||||
596,564 backlinks. That is the number that goes in a slide, and it is close to meaningless on its own.
|
||||
|
||||
The number underneath it is the one that matters: 13,980 referring domains. Divide the first by the second and you get roughly forty-three links per domain. Nearly every one of those 596,564 links is a repeat vote from a site that had already voted. Sitewide footers, template navigation, franchise directories, and blogroll modules generate link counts in the hundreds of thousands without generating any new endorsement.
|
||||
|
||||
The realistic reading of that profile is not "they have 596,564 links I need to match." It is "they have earned links from about fourteen thousand distinct sites, and I need to understand which of those are reachable."
|
||||
|
||||
Bradley Benner, who runs link building at Semantic Mastery, argues the third-party numbers deserve even less weight than that:
|
||||
|
||||
> And so I totally believe 100% wholeheartedly that third-party metrics are useless. What we should be focusing on is relevance to strengthen those entity associations.
|
||||
|
||||
He allows one legitimate use for them, and the ordering is the point:
|
||||
|
||||
> If you want to use a third-party metric like DA, DR, Trust Flow, whatever, as kind of a secondary metric after determining that a potential link source is relevant to what it's going to be linking to, then fine.
|
||||
|
||||
Relevance first, metric second. A link profile report is a shortlist generator, not a scoreboard.
|
||||
|
||||
## The metric everyone chases and nobody can spend
|
||||
|
||||
The other headline on that panel is rank, a 0-100 authority score. It is the number most often set as an objective, and it is the one least connected to outcomes.
|
||||
|
||||
Ben Senescu, who founded OpenSEO, sees the pattern constantly among founders:
|
||||
|
||||
> A lot of people want to increase their domain rating, entrepreneurs especially, because that's what everyone talks about and what companies are marketing. Like, we'll buy you all these backlinks to increase your domain rating. But is that going to get you any organic traffic?
|
||||
|
||||
The question answers itself for most link campaigns. Authority scores are third-party inventions calculated from link graphs. Google does not read them. They correlate with ranking because both correlate with having a lot of good links, which means moving the score without earning the links moves nothing.
|
||||
|
||||
Read the score as a rough tier, not a target. It tells you whether a competitor is in a different weight class or roughly your own, which is a genuinely useful thing to know before you decide whether to compete on links at all.
|
||||
|
||||
## The two numbers nobody reads, and the opportunity in them
|
||||
|
||||
Look at the bottom half of that same panel. Backlink spam score of 23.0. Broken backlinks: 2,217. Broken pages: 529.
|
||||
|
||||
**Spam score** describes the neighbourhood a site's links come from. A profile in the low twenties is normal for a large brand that has accumulated directory listings and scraper mentions over two decades. What it tells you is that a meaningful share of the fourteen thousand referring domains are not endorsements you would want or could use. Filter them out before you treat the remainder as a target list.
|
||||
|
||||
**Broken backlinks and broken pages** are the interesting ones. 2,217 live links point at URLs on that domain that no longer resolve, spread across 529 dead pages. Every one of those is a site that decided this topic was worth linking to, whose link now goes nowhere.
|
||||
|
||||
That is the most reachable link opportunity on the board, and it does not require you to out-produce anybody. You find the dead page, you find who still links to it, you check whether you have something that genuinely serves the same purpose, and you tell them. It works because you are not asking for a favour. You are reporting a broken link on their site and offering the fix.
|
||||
|
||||
It is also worth running this check against your own domain first. Sites that have been rebuilt more than once frequently discover a double-digit percentage of their own earned links landing on 404s, which is link equity you already paid for and can recover with redirects alone.
|
||||
|
||||
## How to run a link gap in OpenSEO
|
||||
|
||||
As with the keyword gap, OpenSEO does not ship a single "link gap" report that diffs two domains. You pull the [backlink profile](/features/backlink-checker) for each side and let the agent do the comparison, which lets you set the quality filters yourself rather than inheriting somebody's defaults.
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP, compare the link profiles of
|
||||
[mydomain.com] and [competitor.com].
|
||||
|
||||
1. Pull the backlinks overview for both. Report backlinks,
|
||||
referring domains, rank, and spam score side by side. Compute
|
||||
links per referring domain for each, and tell me what that
|
||||
ratio suggests about how their links were acquired.
|
||||
|
||||
2. Pull their backlink profile grouped one per domain, filtered
|
||||
to dofollow links with a low spam score. This is the
|
||||
shortlist, not the full list.
|
||||
|
||||
3. Cross-reference against my referring domains. Return the sites
|
||||
linking to them and not to me, sorted by domain authority, and
|
||||
for each one show which of their pages earned the link.
|
||||
|
||||
4. Separately, pull their broken backlinks: live links pointing
|
||||
at URLs on their domain that no longer resolve. For each,
|
||||
show the linking site and the dead target.
|
||||
|
||||
5. Run step 4 against my own domain too, and tell me how much of
|
||||
my own earned link equity currently lands on a 404.
|
||||
|
||||
For the step 3 list, judge each source on topical relevance to
|
||||
my business first. Tell me which ones you would not approach and
|
||||
why.
|
||||
```
|
||||
|
||||
Step five is the one to run before any of the outreach ones. Recovering links you already earned is cheaper than earning new ones, and it is usually a redirect rather than a campaign.
|
||||
|
||||
## What to do first
|
||||
|
||||
Do not start with the competitor. Start with step five on your own domain. Then, on the competitor side, ignore the link total entirely and read three things: referring domains, links per domain, and broken backlinks.
|
||||
|
||||
If their links-per-domain ratio is high, their advantage is thinner than the headline and their real domain count is within reach. If it is close to one, they have earned links from many distinct sites, which is a genuine moat and probably means competing on content depth rather than on links.
|
||||
|
||||
Either way, the terms you would build pages for come out of [keyword gap analysis](/library/competitive-analysis/keyword-gap-analysis), and whether this domain is even the right one to study comes out of [finding your real competitors](/library/competitive-analysis/find-your-real-competitors). A link profile is the last thing to look at, not the first.
|
||||
|
||||
## Competitor backlink analysis FAQ
|
||||
|
||||
### How do you do a competitor backlink analysis?
|
||||
|
||||
Pull their backlink overview for the headline metrics, then pull the link-level rows grouped one per referring domain so you see distinct sites rather than repeated links. Filter to relevant, low-spam, dofollow sources, then compare that list against your own referring domains. What remains is the reachable gap.
|
||||
|
||||
### What is a good spam score?
|
||||
|
||||
There is no universal threshold, and the score is a third-party estimate rather than a Google signal. Read it comparatively: a large old domain in the low twenties is unremarkable, while a young site with the same score has accumulated links it probably did not earn. Use it to filter a prospect list, not to judge a site.
|
||||
|
||||
### Should I try to get every link my competitor has?
|
||||
|
||||
No. A large share of any competitor's referring domains are directories, scrapers, and syndication that carry no value and would not accept you anyway. The reachable portion is usually a few hundred domains at most, and the relevant portion is smaller still.
|
||||
|
||||
### Why does my competitor have hundreds of thousands of backlinks?
|
||||
|
||||
Almost always because a small number of sites link many times each, through footers, sidebars, templates, or a franchise network. Divide backlinks by referring domains. A ratio far above one means the headline count is repetition rather than endorsement.
|
||||
|
||||
### Are broken competitor backlinks worth chasing?
|
||||
|
||||
They are among the best opportunities available, because the linking site has already demonstrated it will link to this topic and its link is currently broken. The approach works when you genuinely have a better replacement for the dead page, and fails when you are just asking for a swap.
|
||||
123
web/content/marketing/library/competitor-traffic-estimates.mdx
Normal file
123
web/content/marketing/library/competitor-traffic-estimates.mdx
Normal file
@ -0,0 +1,123 @@
|
||||
---
|
||||
title: "How accurate are competitor traffic estimates?"
|
||||
description: "A domain overview reports an estimate, not a measurement. What inflates the headline number, how close-variant stacking works, and how to read a competitor's footprint without being fooled by its size."
|
||||
---
|
||||
|
||||
## What a domain overview actually measures
|
||||
|
||||
Every competitor traffic number you have ever seen is modelled. Nobody outside a company can see its analytics. What a tool does instead is find the keywords a domain ranks for, look up each keyword's estimated search volume, apply an assumed click-through rate for the position held, and add it all up.
|
||||
|
||||
That method is genuinely useful and it is wrong in specific, knowable directions. If you understand the directions, the estimate becomes a good instrument. If you treat it as a measurement, it will lead you into fights you cannot win and away from ones you can.
|
||||
|
||||
Melissa Popp of the agency Rickety Roo points at the largest blind spot, which no volume model can see at all:
|
||||
|
||||
> And then you look at where traffic is actually coming from from competitors, and it's not going to their website. You know, people are starting on Instagram and TikTok.
|
||||
|
||||
An organic traffic estimate covers Google organic. A competitor's real demand may be arriving somewhere the estimate has no visibility into, which cuts both ways: the number can overstate their search position and understate their business.
|
||||
|
||||
## Read the headline number, then read what produced it
|
||||
|
||||
Here is a real domain overview for a national brand in the property restoration category.
|
||||
|
||||

|
||||
|
||||
Three million estimated visits a month, across just under fifty thousand keywords. If you run a restoration company, that is a terrifying number.
|
||||
|
||||
Now read the table under it. The single highest-traffic term is a carpet cleaning query. Second is a commercial cleaning query. Work down the list and a very large share of the estimated traffic sits on carpet cleaning, general house cleaning, air duct cleaning, and dryer vent cleaning.
|
||||
|
||||
That company is not beating you at restoration by three million visits a month. It also runs a cleaning services business, and the cleaning business is where a great deal of the traffic lives. The comparable number, restoration terms only, is a fraction of the headline.
|
||||
|
||||
This is the first and most important correction: **a domain-level traffic estimate aggregates every business line a domain operates.** Before you benchmark against it, filter it down to the business you are actually in.
|
||||
|
||||
## Close-variant stacking, and why keyword counts inflate
|
||||
|
||||
The second correction is subtler and it inflates almost every competitor estimate you will ever read.
|
||||
|
||||

|
||||
|
||||
Look at the block in the middle. Five keywords, all reported at 74,000 monthly searches, all reported at 22,496 estimated traffic, nearly all pointing at the same URL. Above it, three more terms share an identical 165,000 and 26,730, again on one URL.
|
||||
|
||||
Those are not five keywords and three keywords. Search volume data derives from Google Ads, which groups close variants together and reports the group's volume against every member. So one real pool of demand gets reported five times, and if the model then adds up per-keyword traffic estimates, that pool gets counted five times too.
|
||||
|
||||
The practical consequences:
|
||||
|
||||
- **Organic keyword counts are inflated.** "Ranks for 49,475 keywords" includes large families of near-duplicates.
|
||||
- **Estimated traffic is inflated wherever stacking is heavy**, which tends to be in commercial service categories where buyers phrase the same request many ways.
|
||||
- **Two tools will disagree** because they de-duplicate differently and assume different click curves.
|
||||
|
||||
Gert Mellak of Solicom AI Consulting treats that disagreement as information rather than a problem:
|
||||
|
||||
> The narrower the focus, the better you can use the data, because then you can start cross connecting data from different data points. Do Semrush and Ahrefs actually agree? Very often they might not.
|
||||
|
||||
Where two estimates disagree wildly, the honest reading is that the true figure is uncertain, not that one tool is right.
|
||||
|
||||
## Read pages, not keywords
|
||||
|
||||
The fix for both problems is the same. Stop reading the keyword tab and read the pages tab.
|
||||
|
||||
A page count cannot be inflated by close variants, because the five stacked mold terms resolve to one URL. Pages also map to work: you cannot build a keyword, but you can build a page. And grouping by page reveals the shape of a competitor's advantage, which is usually the actual finding.
|
||||
|
||||
Ask three questions of the page list:
|
||||
|
||||
1. **How concentrated is it?** If the top ten pages hold most of the estimated traffic, their moat is ten pages deep and you can see all of it.
|
||||
2. **How many of those pages are in my business?** The restoration example collapses considerably under this question.
|
||||
3. **How old and how good are they?** A dated page holding position three on a term you care about is an invitation.
|
||||
|
||||
Brittany Trafis of Soarion Digital has watched the size advantage matter less than it used to:
|
||||
|
||||
> We are working with brands who have a quarter of the budget of their big competitors in paid advertising, but now within AI search, they're starting to show up and have a higher presence than their larger brands.
|
||||
|
||||
Budget and traffic estimates measure the same thing, which is how much a company has already spent. Neither measures whether a specific page can be beaten.
|
||||
|
||||
## Do it with OpenSEO
|
||||
|
||||
The [domain overview tool](/features/domain-overview) returns the footprint, and the ranked-keyword rows behind it are what let you check the estimate rather than trust it. Connect the [OpenSEO MCP](/docs/mcp) and run the whole correction in one pass.
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP, give me an honest read on [competitor.com].
|
||||
|
||||
1. Pull the domain overview: estimated organic traffic and
|
||||
organic keyword count. Report these as the headline figures.
|
||||
|
||||
2. Pull their ranked keywords sorted by traffic. Find rows that
|
||||
share an identical search volume and identical traffic
|
||||
estimate and resolve to the same URL. Report roughly how much
|
||||
of the headline traffic sits in those stacked groups.
|
||||
|
||||
3. Group the keywords by ranking URL. Give me their top 20 pages
|
||||
by estimated traffic.
|
||||
|
||||
4. Split those pages into "in my business" and "other business
|
||||
line", where my business is [describe what you actually sell].
|
||||
Total each side.
|
||||
|
||||
Finish with two numbers: the headline estimate, and your best
|
||||
estimate for the part of it that competes with me. Say how
|
||||
confident you are and why.
|
||||
```
|
||||
|
||||
That final instruction matters. An estimate presented without a confidence statement invites the reader to treat it as a measurement, which is the failure this whole page exists to prevent.
|
||||
|
||||
## What to do first
|
||||
|
||||
Take the competitor whose traffic number most intimidates you and run step four only. Split their footprint into your business and everything else. In most markets the number that comes back is between a third and a tenth of the headline, and the gap you were planning around was never the gap.
|
||||
|
||||
Then take the terms that survive into [keyword gap analysis](/library/competitive-analysis/keyword-gap-analysis), and check whether the domains you are comparing against are even [the right competitors](/library/competitive-analysis/find-your-real-competitors) before you commit a quarter to catching them.
|
||||
|
||||
## Competitor traffic estimate FAQ
|
||||
|
||||
### How accurate are competitor traffic estimates?
|
||||
|
||||
They are directionally useful and numerically unreliable. They are modelled from ranked keywords, estimated search volumes, and assumed click-through rates, so they inherit every error in all three. Treat an estimate as an order of magnitude and a trend, never as a figure to plan revenue against.
|
||||
|
||||
### Why do different SEO tools show different traffic for the same site?
|
||||
|
||||
Because each one uses a different keyword index, a different volume source, a different way of de-duplicating close variants, and a different click curve. Disagreement between tools is normal and tells you the true figure is uncertain.
|
||||
|
||||
### Why do several keywords show exactly the same search volume?
|
||||
|
||||
Google Ads groups close variants such as plurals, word order changes, and near-synonyms, then reports the grouped volume for every term in the group. Tools that source volume from Google Ads inherit that grouping, which is why five phrasings of one request can all report an identical number.
|
||||
|
||||
### Is estimated organic traffic the same as a website traffic checker?
|
||||
|
||||
No. A traffic checker tries to estimate all visits from every source. An organic traffic estimate covers Google organic search only, and says nothing about direct, email, paid, social, or referral traffic, which for many businesses is the majority.
|
||||
108
web/content/marketing/library/find-your-real-competitors.mdx
Normal file
108
web/content/marketing/library/find-your-real-competitors.mdx
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
title: "The competitors on your battlecard are not the ones in your SERPs"
|
||||
description: "The domains you lose clicks to are rarely the companies you lose deals to. How to compare a keyword set, read the list that comes back, and work out which of those results you can actually take."
|
||||
---
|
||||
|
||||
## How do you find out who your SEO competitors are?
|
||||
|
||||
Ask a founder to name their competitors and you get the sales answer: the three companies that show up in deal reviews. That list is real, and it is the wrong input for a content plan. Search does not rank companies. It ranks pages. The page you have to beat for a term you care about is frequently published by someone who is not in your market at all.
|
||||
|
||||
The fix is to stop naming competitors and start measuring them. Take a keyword set you genuinely want to own, compare every domain that appears across those SERPs, and rank them by how much of that result space they hold. What comes back is not a competitor list. It is a list of who currently stands between you and the clicks.
|
||||
|
||||
Jeremy Moser, who runs the agency uSERP, describes this as the first thing his team fixes on a new engagement:
|
||||
|
||||
> A lot of folks will come in and say... these are the main core central themes or topics that we want to rank for long-term. But then when you actually go to the search results and you look and see what's there, sometimes it's just not even worth it.
|
||||
|
||||
The topic list and the SERP disagree more often than anyone expects. Measuring is what tells you which one to believe.
|
||||
|
||||
## Why your SERP competitors are not your business competitors
|
||||
|
||||
Here is a real comparison across five terms in the property restoration category: the head terms, plus one city-modified term.
|
||||
|
||||
The top result by visibility was the national brand everyone in that industry names first. No surprise. But fifth on the list was Yelp. Tenth was Home Depot. Twelfth was YouTube.
|
||||
|
||||
None of those three sell restoration services. All three occupy positions a restoration company would like to hold. A directory, a big-box retailer, and a video platform were collectively taking more of that result space than most of the actual restoration firms in the comparison.
|
||||
|
||||
That changes what you build. You do not out-content Yelp with a better service page. You either target the terms where a directory does not belong, or you accept that a share of that SERP is structurally unavailable and price your forecast accordingly.
|
||||
|
||||
The reverse surprise is just as useful. On the city-modified term, the domain sitting at position one was a small local operator nobody would have listed. The national brand with millions of visits a month was at position three, on its own category term, in a single city. Scale did not decide that result. Relevance did.
|
||||
|
||||
## The three groups every competitor list splits into
|
||||
|
||||
Once you have the measured list, sort it into three buckets before you plan anything.
|
||||
|
||||
1. **Direct competitors** who sell what you sell and rank for what you want. These are the ones worth a full [competitor analysis](/docs/skills/competitor-analysis), because everything they rank for is a page you could plausibly build.
|
||||
2. **Structural competitors** like directories, marketplaces, review sites, and video. You will rarely displace them, but you can often join them. If Yelp holds a position on your term, being well-placed inside Yelp is a cheaper win than trying to rank above it.
|
||||
3. **Accidental competitors** who rank on one term because of a single strong page, usually a guide or a glossary entry. They are the softest targets on the list. One page beat you, so one better page can take it back.
|
||||
|
||||
Most competitive analysis fails because it treats all three as group one.
|
||||
|
||||
There is a fourth category worth holding in mind even though no tool will surface it. Jorge Chavez, who consults on sales process at Topaz Sales Consulting, makes the point that the competitor named in a deal review is usually not the one that wins:
|
||||
|
||||
> You could ask anybody in any podcast you ever do, who's your biggest competitor? I suspect nine out of ten will tell you a name of a competitor... But the real answer is no change.
|
||||
|
||||
Search has the same problem. Plenty of the demand you are measuring ends in nobody buying anything. A competitor list tells you who takes the clicks. It does not tell you which of those clicks were ever going to become customers, which is why the intent filter matters as much as the visibility ranking.
|
||||
|
||||
## How to run this in OpenSEO
|
||||
|
||||
Comparing domains across a keyword set is one of the [competitive research tools](/docs/mcp) in the MCP.
|
||||
|
||||

|
||||
|
||||
Note that Find SERP competitors runs through the MCP and the agent rather than through a page in the app. That is fine, and it is arguably the better shape: the useful version of this question involves a keyword set, an exclusion list, and a judgement call about each result, which is a conversation rather than a form.
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP, find the SERP competitors for [mydomain.com].
|
||||
|
||||
1. Compare these keywords: [list 5-15 terms you actually want to
|
||||
own, including at least one modified the way a real buyer
|
||||
would modify it]. Exclude my own domain from the results.
|
||||
|
||||
2. Return the domains sorted by visibility, and for each one show
|
||||
which of my keywords it ranks for and at what position.
|
||||
|
||||
3. Sort the list into three groups:
|
||||
- direct competitors who sell what I sell
|
||||
- structural results (directories, marketplaces, review sites,
|
||||
video, retailers) that I am unlikely to outrank
|
||||
- accidental competitors ranking on a single strong page
|
||||
|
||||
4. For group three, tell me which single URL is ranking and what
|
||||
it would take to beat it.
|
||||
|
||||
Flag anything in the list I would not have guessed.
|
||||
```
|
||||
|
||||
That last line is the one that earns its keep. The value of this workflow is not confirmation. It is the names you did not expect.
|
||||
|
||||
## Choosing the keyword set you compare on
|
||||
|
||||
The output is only as good as the terms you feed it, and the most common mistake is feeding it your own vocabulary. If you compare on the words your marketing site uses, you get back the domains that also use your words, which is a much smaller and friendlier world than the one you actually sell into.
|
||||
|
||||
Seed the comparison from customer language instead. The same discipline that governs [seeding keywords from conversation](/library/keyword-research/seed-from-conversation) governs this: use the terms buyers type, not the terms your category page was written around. And keep at least one modified term in the set. Head terms tell you who is big. Modified terms tell you who is beatable.
|
||||
|
||||
## What to do first
|
||||
|
||||
Pick five terms you would be happy to rank for next quarter, run the comparison, and read the list without editing it. Then answer one question for each unexpected domain: is this a company, a platform, or a page?
|
||||
|
||||
Companies go into a full competitor analysis. Platforms go into a distribution plan. Pages go into your content backlog, and they are usually the fastest wins on the board.
|
||||
|
||||
From there, the next step is measuring the distance. Once you know who ranks, [keyword gap analysis](/library/competitive-analysis/keyword-gap-analysis) tells you what they hold that you do not, and [reading their domain overview honestly](/library/competitive-analysis/competitor-traffic-estimates) tells you whether their advantage is as large as it looks.
|
||||
|
||||
## Finding SEO competitors FAQ
|
||||
|
||||
### How do you find your competitors' websites?
|
||||
|
||||
Compare a set of keywords you want to rank for and look at which domains appear across those results. That measured list is more reliable than a list assembled from memory, because it is built from the SERPs you are actually competing in rather than from the companies you meet in sales calls.
|
||||
|
||||
### What are the types of competitor analysis?
|
||||
|
||||
For search work, three groupings matter more than any formal framework: direct competitors selling what you sell, structural results like directories and marketplaces that own positions by category rather than by merit, and accidental competitors ranking on one strong page. Each one calls for a different response.
|
||||
|
||||
### Should you target the same keywords as your competitors?
|
||||
|
||||
Only where you can plausibly win and the term matches something you sell. A competitor ranking for a term is evidence of demand, not evidence that you should chase it. Terms held by directories and marketplaces are often better joined than fought, and terms held by a competitor's single strong page are usually the cheapest to take.
|
||||
|
||||
### Why does a small site outrank a big brand on some terms?
|
||||
|
||||
Because relevance is scored per page, not per company. A page written specifically for one query, in one city, for one intent, routinely beats a national brand's generic category page on that query. This is the single most useful thing a measured competitor list will show you.
|
||||
110
web/content/marketing/library/keyword-gap-analysis.mdx
Normal file
110
web/content/marketing/library/keyword-gap-analysis.mdx
Normal file
@ -0,0 +1,110 @@
|
||||
---
|
||||
title: "Keyword gap analysis: subtract the brand terms first"
|
||||
description: "A raw keyword gap between you and a competitor is mostly noise, because most ranked-keyword lists are mostly brand. How to strip brand from both sides and turn the remainder into a page plan."
|
||||
---
|
||||
|
||||
## What is a keyword gap analysis?
|
||||
|
||||
A keyword gap analysis compares the keywords a competitor ranks for against the keywords you rank for, and treats the difference as your opportunity list. It is the single most requested competitive report, and in its raw form it is close to useless.
|
||||
|
||||
The reason is arithmetic. Pull the ranked keywords for a well-established competitor and a smaller site in the same market and the two numbers are not comparable. Here is a real pair from the property restoration category: the national brand ranks for 49,475 organic keywords. The regional operator ranks for 79.
|
||||
|
||||
A raw gap analysis reports that as 49,396 opportunities. That number is not a plan. It is a way of feeling behind.
|
||||
|
||||
## Why your ranked-keyword list is mostly your own name
|
||||
|
||||
Look at what the smaller site actually ranks for, sorted by traffic.
|
||||
|
||||

|
||||
|
||||
Every row at the top of that list is the company's own name in some arrangement. Below the fold it gets stranger: the site also ranks for several _other_ restoration companies' brand names, for a street address, and for a local civic app that happens to share a word with one of its city pages.
|
||||
|
||||
Strip all of that out and the genuine service rankings are a short list: one city plus water damage at position 19, one city plus mold removal at position 18, one city plus fire damage at position 18. Three real footholds, not 79.
|
||||
|
||||
That is the actual starting position, and it is much better news than the raw number, because three near-miss service pages is a quarter's worth of work rather than an impossible backlog.
|
||||
|
||||
The same subtraction has to run on the competitor's side. Their 49,475 includes their brand, their franchise location pages, their glossary, and a large volume of close variants. The comparable figure on both sides is service terms only.
|
||||
|
||||
## How to run a keyword gap in OpenSEO
|
||||
|
||||
OpenSEO does not have a one-click "keyword gap" report that takes two domains and returns the difference. What it has are the two halves: you pull ranked keywords for each domain and the agent performs the join. In practice this is more flexible than a fixed report, because you control the filters on both sides, but it is worth knowing before you go looking for a button.
|
||||
|
||||
The filter that does the real work is brand exclusion, which is a parameter on the ranked-keywords tool rather than something you have to post-process.
|
||||
|
||||
```text
|
||||
Using the OpenSEO MCP, run a keyword gap between [mydomain.com]
|
||||
and [competitor.com].
|
||||
|
||||
1. Pull ranked keywords for both domains. On both sides, exclude
|
||||
brand terms: [my brand and its variants] and [their brand,
|
||||
their parent company, their location-page naming pattern].
|
||||
|
||||
2. Drop anything that is a competitor's brand name, a street
|
||||
address, or an obvious mismatch. Show me what you dropped and
|
||||
why, in a short list.
|
||||
|
||||
3. Return three tables:
|
||||
- terms they rank top 10 for and I do not rank for at all
|
||||
- terms we both rank for where they are ahead of me
|
||||
- terms I rank 11-30 for that they do not hold, which is
|
||||
defensible ground rather than a gap
|
||||
|
||||
4. For table one, group by the page on their site that ranks, so
|
||||
I can see whether the gap is many pages or one strong page.
|
||||
|
||||
Sort each table by search volume and tell me which table you
|
||||
would work first.
|
||||
```
|
||||
|
||||
Table three is the one most gap reports omit and the one most worth having. A gap analysis that only looks at what you are missing will walk you away from the terms you are quietly already winning.
|
||||
|
||||
## Learn from the gap, do not copy it
|
||||
|
||||
The output of this workflow is a list of things a competitor has that you do not. The obvious move is to build the same pages. It is usually the wrong move.
|
||||
|
||||
Tom Malesic, who runs the agency EZMarketing, put the objection plainly when asked how to use competitor research without producing a copy of the competitor:
|
||||
|
||||
> So for small business owners, their competitors are generally small business owners. And most small business owners' marketing is terrible. So I worry less about sounding like the competitor, and I worry more about creating the voice and sounding like the business that I'm representing.
|
||||
|
||||
A gap tells you a topic has demand and that somebody satisfied it well enough to rank. It does not tell you that their page is good. Frequently the page holding position four is thin, dated, and beatable by anyone willing to answer the question properly.
|
||||
|
||||
Gert Mellak of Solicom AI Consulting frames the useful version as a narrowing exercise rather than a copying one:
|
||||
|
||||
> Let's really analyze those five. Let's go to competitors, let's see what's working for them, what can we learn from them? How can we go deeper on those five topics, angles, aspects?
|
||||
|
||||
Five is closer to the right number than five hundred. A gap report's value is in the top of the list, not its length.
|
||||
|
||||
## Turning the gap into pages
|
||||
|
||||
Once the list is clean, it stops being a competitive artefact and becomes an ordinary content plan. Group the surviving terms by intent, then by the page that would satisfy them, exactly as you would with any other keyword set. The workflows in [clustering keywords into topical hubs](/library/keyword-research/cluster-topical-hubs) and [search-intent mapping](/library/keyword-research/search-intent-mapping) apply here without modification, because at this point the source of the keywords no longer matters.
|
||||
|
||||
Two rules specific to gap-sourced keywords:
|
||||
|
||||
1. **Check the ranking page before you commit.** If a competitor holds thirty terms with one page, you need one page, not thirty. Grouping by their URL is what reveals this.
|
||||
2. **Do not chase terms your product does not serve.** A competitor with a broader product line will rank for things you have no business ranking for. The restoration comparison above is a good example: a large share of the national brand's traffic comes from general cleaning services, which is a different business.
|
||||
|
||||
That second point is developed further in [reading a competitor's domain overview honestly](/library/competitive-analysis/competitor-traffic-estimates).
|
||||
|
||||
## What to do first
|
||||
|
||||
Run the gap on one competitor, with brand excluded on both sides, and stop at the first ten rows. Pick the one row where a competitor's page is visibly weaker than what you could write, and write that page. A gap analysis that produces one shipped page has outperformed the vast majority of gap analyses, which produce a spreadsheet.
|
||||
|
||||
Then run it again in ninety days on the same competitor. The change between the two runs is more informative than either run alone.
|
||||
|
||||
## Keyword gap analysis FAQ
|
||||
|
||||
### What is a keyword gap in SEO?
|
||||
|
||||
The set of keywords a competitor ranks for and you do not. Useful only after brand terms are removed from both sides, because otherwise the largest part of the difference is simply that you have different company names.
|
||||
|
||||
### How do you do a content gap analysis for free?
|
||||
|
||||
The discovery half costs nothing: read the competitor's pages, note what they cover that you do not, and check your own Search Console for questions you already receive impressions for. The part that costs money is the ranked-keyword data on the competitor's side, which is why quality SEO data sits behind a subscription in every tool. OpenSEO is open source and free to start, with a paid plan at $10/month that includes usage credits.
|
||||
|
||||
### Should I target every keyword my competitor ranks for?
|
||||
|
||||
No. A competitor's ranked-keyword list includes their brand, their variants, and terms served by product lines you do not have. The list worth acting on is usually a few dozen rows, not a few thousand.
|
||||
|
||||
### What if my competitor ranks for thousands of keywords and I rank for almost none?
|
||||
|
||||
Group their keywords by the page that ranks. A large keyword count is often a small number of strong pages, and a small number of strong pages is something you can compete with. The count itself is a poor measure of how far ahead they are.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@ -2,13 +2,22 @@ import type { ReactNode } from "react";
|
||||
import { DocsBody } from "fumadocs-ui/page";
|
||||
import { buildBreadcrumbJsonLd } from "@/lib/seo";
|
||||
|
||||
const KEYWORD_RESEARCH_LIBRARY_PATH = "/library/keyword-research";
|
||||
const KEYWORD_RESEARCH_LIBRARY = {
|
||||
name: "Keyword Research",
|
||||
path: "/library/keyword-research",
|
||||
};
|
||||
|
||||
type LibraryRef = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
type LibrarySpokePageProps = {
|
||||
title: string;
|
||||
description?: string;
|
||||
crumb: string;
|
||||
path: string;
|
||||
library?: LibraryRef;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
@ -17,11 +26,12 @@ export function LibrarySpokePage({
|
||||
description,
|
||||
crumb,
|
||||
path,
|
||||
library = KEYWORD_RESEARCH_LIBRARY,
|
||||
children,
|
||||
}: LibrarySpokePageProps) {
|
||||
const breadcrumbLd = buildBreadcrumbJsonLd([
|
||||
{ name: "Strategy Library", path: "/library" },
|
||||
{ name: "Keyword Research", path: KEYWORD_RESEARCH_LIBRARY_PATH },
|
||||
{ name: library.name, path: library.path },
|
||||
{ name: crumb, path },
|
||||
]);
|
||||
|
||||
@ -40,10 +50,10 @@ export function LibrarySpokePage({
|
||||
</a>{" "}
|
||||
/{" "}
|
||||
<a
|
||||
href={KEYWORD_RESEARCH_LIBRARY_PATH}
|
||||
href={library.path}
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Keyword Research
|
||||
{library.name}
|
||||
</a>{" "}
|
||||
/ <span>{crumb}</span>
|
||||
</nav>
|
||||
@ -61,7 +71,7 @@ export function LibrarySpokePage({
|
||||
{children}
|
||||
</DocsBody>
|
||||
|
||||
<LibrarySpokeCta />
|
||||
<LibrarySpokeCta library={library} />
|
||||
|
||||
<script
|
||||
type="application/ld+json"
|
||||
@ -72,7 +82,7 @@ export function LibrarySpokePage({
|
||||
);
|
||||
}
|
||||
|
||||
function LibrarySpokeCta() {
|
||||
function LibrarySpokeCta({ library }: { library: LibraryRef }) {
|
||||
return (
|
||||
<section className="mt-14 rounded-xl border border-[var(--color-border-subtle)] bg-white p-6">
|
||||
<p className="text-xl font-semibold tracking-tight text-neutral-950">
|
||||
@ -93,10 +103,10 @@ function LibrarySpokeCta() {
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href={KEYWORD_RESEARCH_LIBRARY_PATH}
|
||||
href={library.path}
|
||||
className="inline-flex h-10 items-center justify-center rounded-lg border border-[var(--color-border-subtle)] bg-white px-4 text-sm font-medium text-neutral-950 transition-colors hover:border-neutral-950"
|
||||
>
|
||||
Back to Keyword Research
|
||||
Back to {library.name}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -395,6 +395,41 @@ export const featurePages = {
|
||||
"Not quite. It includes an estimated-traffic metric, but the value is seeing which keywords and pages produce that traffic, which a plain traffic checker doesn't show.",
|
||||
},
|
||||
],
|
||||
guides: {
|
||||
title: "The Competitive Analysis Strategy Library",
|
||||
description:
|
||||
"Practitioner strategies for turning a domain overview into a decision. Each guide includes a full walkthrough and a copy-paste MCP prompt.",
|
||||
items: [
|
||||
{
|
||||
label: "Find out who your real competitors are",
|
||||
description:
|
||||
"Compare a keyword set and read the domains actually in your SERPs.",
|
||||
href: "/library/competitive-analysis/find-your-real-competitors",
|
||||
},
|
||||
{
|
||||
label: "Keyword gap analysis: subtract the brand terms first",
|
||||
description:
|
||||
"Strip brand from both sides and the gap becomes buildable.",
|
||||
href: "/library/competitive-analysis/keyword-gap-analysis",
|
||||
},
|
||||
{
|
||||
label: "How accurate are competitor traffic estimates?",
|
||||
description:
|
||||
"Close-variant stacking, other business lines, and how to correct for both.",
|
||||
href: "/library/competitive-analysis/competitor-traffic-estimates",
|
||||
},
|
||||
{
|
||||
label: "Read a competitor's link profile before you copy it",
|
||||
description:
|
||||
"Referring domains, spam score, and the broken links worth chasing.",
|
||||
href: "/library/competitive-analysis/backlink-gap-analysis",
|
||||
},
|
||||
],
|
||||
cta: {
|
||||
label: "Browse all competitive analysis strategies",
|
||||
href: "/library/competitive-analysis",
|
||||
},
|
||||
},
|
||||
},
|
||||
rankTracking: {
|
||||
slug: FEATURE_PAGE_SLUGS.rankTracking,
|
||||
|
||||
@ -53,3 +53,35 @@ export const keywordResearchStrategies: StrategyLibraryItem[] = [
|
||||
href: "/library/keyword-research/positioning-to-demand",
|
||||
},
|
||||
];
|
||||
|
||||
export const COMPETITIVE_ANALYSIS_LIBRARY = {
|
||||
name: "Competitive Analysis",
|
||||
path: "/library/competitive-analysis",
|
||||
};
|
||||
|
||||
export const competitiveAnalysisStrategies: StrategyLibraryItem[] = [
|
||||
{
|
||||
title: "Find out who your real competitors are",
|
||||
description:
|
||||
"The domains sharing your SERPs are rarely the companies on your battlecard. Compare a keyword set and read the list you actually compete against.",
|
||||
href: "/library/competitive-analysis/find-your-real-competitors",
|
||||
},
|
||||
{
|
||||
title: "Keyword gap analysis: subtract the brand terms first",
|
||||
description:
|
||||
"Most ranked-keyword lists are mostly brand. Strip brand from both sides and the gap becomes a short, buildable list.",
|
||||
href: "/library/competitive-analysis/keyword-gap-analysis",
|
||||
},
|
||||
{
|
||||
title: "How accurate are competitor traffic estimates?",
|
||||
description:
|
||||
"Read a domain overview without being fooled by close-variant stacking or a headline traffic number from another business line.",
|
||||
href: "/library/competitive-analysis/competitor-traffic-estimates",
|
||||
},
|
||||
{
|
||||
title: "Read a competitor's link profile before you copy it",
|
||||
description:
|
||||
"Referring domains, spam score, and broken links tell you whether an authority advantage is real or repeated.",
|
||||
href: "/library/competitive-analysis/backlink-gap-analysis",
|
||||
},
|
||||
];
|
||||
|
||||
@ -41,6 +41,7 @@ import { Route as MarketingFeaturesBacklinkCheckerRouteImport } from './routes/_
|
||||
import { Route as MarketingFeaturesAiSearchPromptsRouteImport } from './routes/_marketing/features/ai-search-prompts'
|
||||
import { Route as MarketingFeaturesAiBrandVisibilityRouteImport } from './routes/_marketing/features/ai-brand-visibility'
|
||||
import { Route as MarketingLibraryKeywordResearchIndexRouteImport } from './routes/_marketing/library/keyword-research/index'
|
||||
import { Route as MarketingLibraryCompetitiveAnalysisIndexRouteImport } from './routes/_marketing/library/competitive-analysis/index'
|
||||
import { Route as MarketingLibraryKeywordResearchSeedFromConversationRouteImport } from './routes/_marketing/library/keyword-research/seed-from-conversation'
|
||||
import { Route as MarketingLibraryKeywordResearchSearchIntentMappingRouteImport } from './routes/_marketing/library/keyword-research/search-intent-mapping'
|
||||
import { Route as MarketingLibraryKeywordResearchPositioningToDemandRouteImport } from './routes/_marketing/library/keyword-research/positioning-to-demand'
|
||||
@ -49,6 +50,10 @@ import { Route as MarketingLibraryKeywordResearchLongTailQuestionMiningRouteImpo
|
||||
import { Route as MarketingLibraryKeywordResearchIntentBeyondGoogleRouteImport } from './routes/_marketing/library/keyword-research/intent-beyond-google'
|
||||
import { Route as MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRouteImport } from './routes/_marketing/library/keyword-research/gsc-programmatic-discovery'
|
||||
import { Route as MarketingLibraryKeywordResearchClusterTopicalHubsRouteImport } from './routes/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
import { Route as MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRouteImport } from './routes/_marketing/library/competitive-analysis/keyword-gap-analysis'
|
||||
import { Route as MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRouteImport } from './routes/_marketing/library/competitive-analysis/find-your-real-competitors'
|
||||
import { Route as MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRouteImport } from './routes/_marketing/library/competitive-analysis/competitor-traffic-estimates'
|
||||
import { Route as MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRouteImport } from './routes/_marketing/library/competitive-analysis/backlink-gap-analysis'
|
||||
|
||||
const TermsAndConditionsRoute = TermsAndConditionsRouteImport.update({
|
||||
id: '/terms-and-conditions',
|
||||
@ -221,6 +226,12 @@ const MarketingLibraryKeywordResearchIndexRoute =
|
||||
path: '/library/keyword-research/',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryCompetitiveAnalysisIndexRoute =
|
||||
MarketingLibraryCompetitiveAnalysisIndexRouteImport.update({
|
||||
id: '/library/competitive-analysis/',
|
||||
path: '/library/competitive-analysis/',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryKeywordResearchSeedFromConversationRoute =
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRouteImport.update({
|
||||
id: '/library/keyword-research/seed-from-conversation',
|
||||
@ -271,6 +282,32 @@ const MarketingLibraryKeywordResearchClusterTopicalHubsRoute =
|
||||
path: '/library/keyword-research/cluster-topical-hubs',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute =
|
||||
MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRouteImport.update({
|
||||
id: '/library/competitive-analysis/keyword-gap-analysis',
|
||||
path: '/library/competitive-analysis/keyword-gap-analysis',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute =
|
||||
MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRouteImport.update({
|
||||
id: '/library/competitive-analysis/find-your-real-competitors',
|
||||
path: '/library/competitive-analysis/find-your-real-competitors',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
const MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute =
|
||||
MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRouteImport.update(
|
||||
{
|
||||
id: '/library/competitive-analysis/competitor-traffic-estimates',
|
||||
path: '/library/competitive-analysis/competitor-traffic-estimates',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any,
|
||||
)
|
||||
const MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute =
|
||||
MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRouteImport.update({
|
||||
id: '/library/competitive-analysis/backlink-gap-analysis',
|
||||
path: '/library/competitive-analysis/backlink-gap-analysis',
|
||||
getParentRoute: () => MarketingRoute,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof MarketingIndexRoute
|
||||
@ -303,6 +340,10 @@ export interface FileRoutesByFullPath {
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/library/': typeof MarketingLibraryIndexRoute
|
||||
'/library/competitive-analysis/backlink-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute
|
||||
'/library/competitive-analysis/competitor-traffic-estimates': typeof MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute
|
||||
'/library/competitive-analysis/find-your-real-competitors': typeof MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute
|
||||
'/library/competitive-analysis/keyword-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -311,6 +352,7 @@ export interface FileRoutesByFullPath {
|
||||
'/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute
|
||||
'/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/library/competitive-analysis/': typeof MarketingLibraryCompetitiveAnalysisIndexRoute
|
||||
'/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
@ -344,6 +386,10 @@ export interface FileRoutesByTo {
|
||||
'/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/features': typeof MarketingFeaturesIndexRoute
|
||||
'/library': typeof MarketingLibraryIndexRoute
|
||||
'/library/competitive-analysis/backlink-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute
|
||||
'/library/competitive-analysis/competitor-traffic-estimates': typeof MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute
|
||||
'/library/competitive-analysis/find-your-real-competitors': typeof MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute
|
||||
'/library/competitive-analysis/keyword-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute
|
||||
'/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -352,6 +398,7 @@ export interface FileRoutesByTo {
|
||||
'/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute
|
||||
'/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/library/competitive-analysis': typeof MarketingLibraryCompetitiveAnalysisIndexRoute
|
||||
'/library/keyword-research': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
@ -387,6 +434,10 @@ export interface FileRoutesById {
|
||||
'/_marketing/features/site-audit': typeof MarketingFeaturesSiteAuditRoute
|
||||
'/_marketing/features/': typeof MarketingFeaturesIndexRoute
|
||||
'/_marketing/library/': typeof MarketingLibraryIndexRoute
|
||||
'/_marketing/library/competitive-analysis/backlink-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute
|
||||
'/_marketing/library/competitive-analysis/competitor-traffic-estimates': typeof MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute
|
||||
'/_marketing/library/competitive-analysis/find-your-real-competitors': typeof MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute
|
||||
'/_marketing/library/competitive-analysis/keyword-gap-analysis': typeof MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute
|
||||
'/_marketing/library/keyword-research/cluster-topical-hubs': typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
'/_marketing/library/keyword-research/gsc-programmatic-discovery': typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
'/_marketing/library/keyword-research/intent-beyond-google': typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -395,6 +446,7 @@ export interface FileRoutesById {
|
||||
'/_marketing/library/keyword-research/positioning-to-demand': typeof MarketingLibraryKeywordResearchPositioningToDemandRoute
|
||||
'/_marketing/library/keyword-research/search-intent-mapping': typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
'/_marketing/library/keyword-research/seed-from-conversation': typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
'/_marketing/library/competitive-analysis/': typeof MarketingLibraryCompetitiveAnalysisIndexRoute
|
||||
'/_marketing/library/keyword-research/': typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
@ -430,6 +482,10 @@ export interface FileRouteTypes {
|
||||
| '/features/site-audit'
|
||||
| '/features/'
|
||||
| '/library/'
|
||||
| '/library/competitive-analysis/backlink-gap-analysis'
|
||||
| '/library/competitive-analysis/competitor-traffic-estimates'
|
||||
| '/library/competitive-analysis/find-your-real-competitors'
|
||||
| '/library/competitive-analysis/keyword-gap-analysis'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/library/keyword-research/intent-beyond-google'
|
||||
@ -438,6 +494,7 @@ export interface FileRouteTypes {
|
||||
| '/library/keyword-research/positioning-to-demand'
|
||||
| '/library/keyword-research/search-intent-mapping'
|
||||
| '/library/keyword-research/seed-from-conversation'
|
||||
| '/library/competitive-analysis/'
|
||||
| '/library/keyword-research/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
@ -471,6 +528,10 @@ export interface FileRouteTypes {
|
||||
| '/features/site-audit'
|
||||
| '/features'
|
||||
| '/library'
|
||||
| '/library/competitive-analysis/backlink-gap-analysis'
|
||||
| '/library/competitive-analysis/competitor-traffic-estimates'
|
||||
| '/library/competitive-analysis/find-your-real-competitors'
|
||||
| '/library/competitive-analysis/keyword-gap-analysis'
|
||||
| '/library/keyword-research/cluster-topical-hubs'
|
||||
| '/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/library/keyword-research/intent-beyond-google'
|
||||
@ -479,6 +540,7 @@ export interface FileRouteTypes {
|
||||
| '/library/keyword-research/positioning-to-demand'
|
||||
| '/library/keyword-research/search-intent-mapping'
|
||||
| '/library/keyword-research/seed-from-conversation'
|
||||
| '/library/competitive-analysis'
|
||||
| '/library/keyword-research'
|
||||
id:
|
||||
| '__root__'
|
||||
@ -513,6 +575,10 @@ export interface FileRouteTypes {
|
||||
| '/_marketing/features/site-audit'
|
||||
| '/_marketing/features/'
|
||||
| '/_marketing/library/'
|
||||
| '/_marketing/library/competitive-analysis/backlink-gap-analysis'
|
||||
| '/_marketing/library/competitive-analysis/competitor-traffic-estimates'
|
||||
| '/_marketing/library/competitive-analysis/find-your-real-competitors'
|
||||
| '/_marketing/library/competitive-analysis/keyword-gap-analysis'
|
||||
| '/_marketing/library/keyword-research/cluster-topical-hubs'
|
||||
| '/_marketing/library/keyword-research/gsc-programmatic-discovery'
|
||||
| '/_marketing/library/keyword-research/intent-beyond-google'
|
||||
@ -521,6 +587,7 @@ export interface FileRouteTypes {
|
||||
| '/_marketing/library/keyword-research/positioning-to-demand'
|
||||
| '/_marketing/library/keyword-research/search-intent-mapping'
|
||||
| '/_marketing/library/keyword-research/seed-from-conversation'
|
||||
| '/_marketing/library/competitive-analysis/'
|
||||
| '/_marketing/library/keyword-research/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
@ -766,6 +833,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchIndexRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/competitive-analysis/': {
|
||||
id: '/_marketing/library/competitive-analysis/'
|
||||
path: '/library/competitive-analysis'
|
||||
fullPath: '/library/competitive-analysis/'
|
||||
preLoaderRoute: typeof MarketingLibraryCompetitiveAnalysisIndexRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/keyword-research/seed-from-conversation': {
|
||||
id: '/_marketing/library/keyword-research/seed-from-conversation'
|
||||
path: '/library/keyword-research/seed-from-conversation'
|
||||
@ -822,6 +896,34 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof MarketingLibraryKeywordResearchClusterTopicalHubsRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/competitive-analysis/keyword-gap-analysis': {
|
||||
id: '/_marketing/library/competitive-analysis/keyword-gap-analysis'
|
||||
path: '/library/competitive-analysis/keyword-gap-analysis'
|
||||
fullPath: '/library/competitive-analysis/keyword-gap-analysis'
|
||||
preLoaderRoute: typeof MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/competitive-analysis/find-your-real-competitors': {
|
||||
id: '/_marketing/library/competitive-analysis/find-your-real-competitors'
|
||||
path: '/library/competitive-analysis/find-your-real-competitors'
|
||||
fullPath: '/library/competitive-analysis/find-your-real-competitors'
|
||||
preLoaderRoute: typeof MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/competitive-analysis/competitor-traffic-estimates': {
|
||||
id: '/_marketing/library/competitive-analysis/competitor-traffic-estimates'
|
||||
path: '/library/competitive-analysis/competitor-traffic-estimates'
|
||||
fullPath: '/library/competitive-analysis/competitor-traffic-estimates'
|
||||
preLoaderRoute: typeof MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
'/_marketing/library/competitive-analysis/backlink-gap-analysis': {
|
||||
id: '/_marketing/library/competitive-analysis/backlink-gap-analysis'
|
||||
path: '/library/competitive-analysis/backlink-gap-analysis'
|
||||
fullPath: '/library/competitive-analysis/backlink-gap-analysis'
|
||||
preLoaderRoute: typeof MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRouteImport
|
||||
parentRoute: typeof MarketingRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -844,6 +946,10 @@ interface MarketingRouteChildren {
|
||||
MarketingFeaturesSiteAuditRoute: typeof MarketingFeaturesSiteAuditRoute
|
||||
MarketingFeaturesIndexRoute: typeof MarketingFeaturesIndexRoute
|
||||
MarketingLibraryIndexRoute: typeof MarketingLibraryIndexRoute
|
||||
MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute: typeof MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute
|
||||
MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute: typeof MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute
|
||||
MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute: typeof MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute
|
||||
MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute: typeof MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute: typeof MarketingLibraryKeywordResearchClusterTopicalHubsRoute
|
||||
MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute: typeof MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute
|
||||
MarketingLibraryKeywordResearchIntentBeyondGoogleRoute: typeof MarketingLibraryKeywordResearchIntentBeyondGoogleRoute
|
||||
@ -852,6 +958,7 @@ interface MarketingRouteChildren {
|
||||
MarketingLibraryKeywordResearchPositioningToDemandRoute: typeof MarketingLibraryKeywordResearchPositioningToDemandRoute
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRoute: typeof MarketingLibraryKeywordResearchSearchIntentMappingRoute
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute: typeof MarketingLibraryKeywordResearchSeedFromConversationRoute
|
||||
MarketingLibraryCompetitiveAnalysisIndexRoute: typeof MarketingLibraryCompetitiveAnalysisIndexRoute
|
||||
MarketingLibraryKeywordResearchIndexRoute: typeof MarketingLibraryKeywordResearchIndexRoute
|
||||
}
|
||||
|
||||
@ -875,6 +982,14 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
MarketingFeaturesSiteAuditRoute: MarketingFeaturesSiteAuditRoute,
|
||||
MarketingFeaturesIndexRoute: MarketingFeaturesIndexRoute,
|
||||
MarketingLibraryIndexRoute: MarketingLibraryIndexRoute,
|
||||
MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute:
|
||||
MarketingLibraryCompetitiveAnalysisBacklinkGapAnalysisRoute,
|
||||
MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute:
|
||||
MarketingLibraryCompetitiveAnalysisCompetitorTrafficEstimatesRoute,
|
||||
MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute:
|
||||
MarketingLibraryCompetitiveAnalysisFindYourRealCompetitorsRoute,
|
||||
MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute:
|
||||
MarketingLibraryCompetitiveAnalysisKeywordGapAnalysisRoute,
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute:
|
||||
MarketingLibraryKeywordResearchClusterTopicalHubsRoute,
|
||||
MarketingLibraryKeywordResearchGscProgrammaticDiscoveryRoute:
|
||||
@ -891,6 +1006,8 @@ const MarketingRouteChildren: MarketingRouteChildren = {
|
||||
MarketingLibraryKeywordResearchSearchIntentMappingRoute,
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute:
|
||||
MarketingLibraryKeywordResearchSeedFromConversationRoute,
|
||||
MarketingLibraryCompetitiveAnalysisIndexRoute:
|
||||
MarketingLibraryCompetitiveAnalysisIndexRoute,
|
||||
MarketingLibraryKeywordResearchIndexRoute:
|
||||
MarketingLibraryKeywordResearchIndexRoute,
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/backlink-gap-analysis.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { COMPETITIVE_ANALYSIS_LIBRARY } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/competitive-analysis/backlink-gap-analysis";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/competitive-analysis/backlink-gap-analysis",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "Competitor Backlink Analysis: Read the Profile First",
|
||||
description: frontmatter.description,
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb={"Read a competitor's link profile before you copy it"}
|
||||
path={PATH}
|
||||
library={COMPETITIVE_ANALYSIS_LIBRARY}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/competitor-traffic-estimates.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { COMPETITIVE_ANALYSIS_LIBRARY } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/competitive-analysis/competitor-traffic-estimates";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/competitive-analysis/competitor-traffic-estimates",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "How Accurate Are Competitor Traffic Estimates?",
|
||||
description: frontmatter.description,
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="How accurate are competitor traffic estimates?"
|
||||
path={PATH}
|
||||
library={COMPETITIVE_ANALYSIS_LIBRARY}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/find-your-real-competitors.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { COMPETITIVE_ANALYSIS_LIBRARY } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/competitive-analysis/find-your-real-competitors";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/competitive-analysis/find-your-real-competitors",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "How to Find Your Real SEO Competitors",
|
||||
description: frontmatter.description,
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Find out who your real competitors are"
|
||||
path={PATH}
|
||||
library={COMPETITIVE_ANALYSIS_LIBRARY}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
321
web/src/routes/_marketing/library/competitive-analysis/index.tsx
Normal file
321
web/src/routes/_marketing/library/competitive-analysis/index.tsx
Normal file
@ -0,0 +1,321 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { buildBreadcrumbJsonLd, buildPageSeo } from "@/lib/seo";
|
||||
import { competitiveAnalysisStrategies } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/competitive-analysis";
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: "What is competitor analysis in SEO?",
|
||||
answer:
|
||||
"Working out which domains hold the search results you want, what they rank for that you do not, and whether their advantage comes from content, links, brand, or a small number of strong pages. It differs from business competitor analysis because search ranks pages, not companies, so the domains taking your clicks are often not the companies taking your deals.",
|
||||
},
|
||||
{
|
||||
question: "How do you find your competitors' websites?",
|
||||
answer:
|
||||
"Compare a set of keywords you want to rank for and look at which domains appear across those results. A measured list built from real SERPs is more reliable than a list assembled from memory, and it will usually include directories, marketplaces, and video platforms alongside the companies you expected.",
|
||||
},
|
||||
{
|
||||
question: "What are the types of competitor analysis?",
|
||||
answer:
|
||||
"For search work, three groupings matter more than any formal framework. Direct competitors sell what you sell and rank for what you want. Structural results such as directories and marketplaces hold positions by category rather than by merit. Accidental competitors rank on a single strong page. Each calls for a different response, and treating all three the same is the most common way a competitive analysis goes wrong.",
|
||||
},
|
||||
{
|
||||
question: "Is there a free competitor analysis tool?",
|
||||
answer:
|
||||
"The reasoning half is free: read the SERPs you care about, read your competitors' pages, and check your own Search Console. The part that costs money is the ranked-keyword and backlink data on the competitor's side, which is why the big SEO suites run $100/month and up. OpenSEO is open source and free to start; the paid plan is $10/month and includes $10 of usage, with top-ups if you need more.",
|
||||
},
|
||||
{
|
||||
question: "How accurate are competitor traffic estimates?",
|
||||
answer:
|
||||
"Directionally useful and numerically unreliable. Estimates are modelled from ranked keywords, estimated search volumes, and assumed click-through rates, so they inherit every error in all three, and they aggregate every business line a domain operates. Treat an estimate as an order of magnitude and a trend, not a number to plan revenue against.",
|
||||
},
|
||||
{
|
||||
question: "What is a keyword gap analysis?",
|
||||
answer:
|
||||
"The set of keywords a competitor ranks for and you do not. It is only useful once brand terms are removed from both sides, because otherwise the largest part of the difference is simply that the two companies have different names.",
|
||||
},
|
||||
];
|
||||
|
||||
const faqLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
"@type": "Question",
|
||||
name: faq.question,
|
||||
acceptedAnswer: { "@type": "Answer", text: faq.answer },
|
||||
})),
|
||||
};
|
||||
|
||||
const breadcrumbLd = buildBreadcrumbJsonLd([
|
||||
{ name: "Strategy Library", path: "/library" },
|
||||
{ name: "Competitive Analysis", path: PATH },
|
||||
]);
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/competitive-analysis/",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "SEO Competitor Analysis: The Strategy Library",
|
||||
description:
|
||||
"Four competitive research strategies for finding who really ranks against you, measuring the gap honestly, and deciding what is worth taking. Each includes a workflow and an OpenSEO MCP prompt.",
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO",
|
||||
}),
|
||||
component: CompetitiveAnalysisLibraryPage,
|
||||
});
|
||||
|
||||
function CompetitiveAnalysisLibraryPage() {
|
||||
return (
|
||||
<article className="mx-auto max-w-5xl">
|
||||
<header className="max-w-3xl">
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="text-sm text-[var(--color-brand-muted)]"
|
||||
>
|
||||
<a
|
||||
href="/library"
|
||||
className="font-medium text-[var(--color-brand-accent)]"
|
||||
>
|
||||
Strategy Library
|
||||
</a>{" "}
|
||||
/ <span>Competitive Analysis</span>
|
||||
</nav>
|
||||
<h1 className="mt-3 text-4xl font-semibold leading-tight tracking-tight text-neutral-950 md:text-6xl">
|
||||
The Competitive Analysis Strategy Library
|
||||
</h1>
|
||||
<p className="mt-5 text-lg leading-8 text-[var(--color-brand-muted)]">
|
||||
Four competitive research strategies for finding who really ranks
|
||||
against you, measuring the gap honestly, and deciding what is worth
|
||||
taking. Each one includes a workflow and a copy-paste OpenSEO MCP
|
||||
prompt.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
How do you do a competitor analysis for SEO?
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Not by filling in a template. Search ranks pages, not companies, so
|
||||
the useful sequence is to measure who holds the results you want,
|
||||
strip the noise out of the comparison, check whether their advantage
|
||||
is as large as the headline number suggests, and only then decide what
|
||||
to build. These four strategies run in that order.
|
||||
</p>
|
||||
<div className="mt-6 grid gap-4 md:grid-cols-2">
|
||||
{competitiveAnalysisStrategies.map((strategy, index) => {
|
||||
const number = String(index + 1).padStart(2, "0");
|
||||
return (
|
||||
<a
|
||||
key={strategy.href}
|
||||
href={strategy.href}
|
||||
className="rounded-lg border border-[var(--color-border-subtle)] bg-white p-5 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
<span className="font-mono text-sm tabular-nums text-[var(--color-brand-accent)]">
|
||||
{number}
|
||||
</span>
|
||||
<h3 className="mt-3 text-base font-semibold text-neutral-950">
|
||||
{strategy.title}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-1 text-[var(--color-brand-accent)]"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{strategy.description}
|
||||
</p>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Why competitor analysis templates fail an SEO
|
||||
</h2>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
Most competitor analysis material is written for a business plan. It
|
||||
asks you to list rivals, tabulate their pricing and positioning, and
|
||||
summarise strengths and weaknesses. That document is worth writing and
|
||||
it will not tell you which page to publish next.
|
||||
</p>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
Search competition is decided per query, per page. The company you
|
||||
lose deals to may be invisible in your results, and a directory you
|
||||
have never thought about may hold three of the ten positions you want.
|
||||
A competitive analysis that is useful for SEO has to start from the
|
||||
SERP and work backwards, which is why every strategy here begins with
|
||||
measured data rather than a list of names.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
What competitor analysis tools actually give you
|
||||
</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Every number in a competitive report is modelled from outside the
|
||||
business it describes. Ranked keywords are real, in that a crawler saw
|
||||
the position. Traffic estimates are not measurements. Authority scores
|
||||
are third-party inventions that Google does not read. Read them as
|
||||
tiers and trends, and the tooling becomes genuinely useful. Read them
|
||||
as facts and you will plan a quarter around a number nobody can spend.
|
||||
</p>
|
||||
<p className="mt-3 max-w-2xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
You can run the data-backed parts of these workflows with OpenSEO's{" "}
|
||||
<a
|
||||
href="/features/domain-overview"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
domain overview
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
href="/features/backlink-checker"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
backlink checker
|
||||
</a>
|
||||
, or through the{" "}
|
||||
<a
|
||||
href="/docs/mcp"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
OpenSEO MCP
|
||||
</a>
|
||||
, which lets a compatible AI assistant compare domains, pull ranked
|
||||
keywords, and read link profiles while it works through the workflow.
|
||||
The{" "}
|
||||
<a
|
||||
href="/docs/skills/competitive-landscape"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
competitive landscape
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
href="/docs/skills/competitor-analysis"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
competitor analysis
|
||||
</a>{" "}
|
||||
agent skills package the same steps as reusable commands.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Where competitive research meets keyword research
|
||||
</h2>
|
||||
<p className="mt-3 max-w-3xl text-sm leading-6 text-neutral-700">
|
||||
A competitive analysis ends with a list of terms, which is where the{" "}
|
||||
<a
|
||||
href="/library/keyword-research"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
keyword research library
|
||||
</a>{" "}
|
||||
picks the work up. The two overlap in three specific places.
|
||||
</p>
|
||||
<ul className="mt-4 max-w-3xl list-disc space-y-2 pl-5 text-sm leading-6 text-neutral-700">
|
||||
<li>
|
||||
The keyword set you compare competitors on should come from customer
|
||||
language, not your own category page, which is the discipline in{" "}
|
||||
<a
|
||||
href="/library/keyword-research/seed-from-conversation"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
seeding from conversation
|
||||
</a>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Gap-sourced keywords still have to be grouped before they become
|
||||
pages, using{" "}
|
||||
<a
|
||||
href="/library/keyword-research/cluster-topical-hubs"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
topical hubs
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
href="/library/keyword-research/search-intent-mapping"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
intent mapping
|
||||
</a>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Your own{" "}
|
||||
<a
|
||||
href="/library/keyword-research/gsc-programmatic-discovery"
|
||||
className="font-medium text-neutral-950 underline decoration-[var(--color-brand-accent)] underline-offset-4"
|
||||
>
|
||||
Search Console queries
|
||||
</a>{" "}
|
||||
are the one dataset in this whole exercise that is measured rather
|
||||
than estimated. Use them to check any competitive number that looks
|
||||
surprising.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Competitor analysis FAQ
|
||||
</h2>
|
||||
<div className="mt-5 divide-y divide-[var(--color-border-subtle)] rounded-lg border border-[var(--color-border-subtle)] bg-white">
|
||||
{faqs.map((faq) => (
|
||||
<div key={faq.question} className="p-5">
|
||||
<h3 className="text-sm font-semibold text-neutral-900">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="mt-1.5 text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
{faq.answer}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mt-12 flex flex-col items-start justify-between gap-4 rounded-xl border border-[var(--color-border-subtle)] bg-white p-6 sm:flex-row sm:items-center md:p-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Run a competitor analysis with your own agent
|
||||
</h2>
|
||||
<p className="mt-2 max-w-xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Each strategy ends with a copy-paste MCP prompt. OpenSEO is open
|
||||
source, free to try, and does not require a credit card.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="https://app.openseo.so/sign-up"
|
||||
className="inline-flex h-11 shrink-0 items-center justify-center rounded-lg bg-neutral-950 px-5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
||||
>
|
||||
Start with OpenSEO
|
||||
<span aria-hidden="true" className="ml-2">
|
||||
→
|
||||
</span>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqLd) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbLd) }}
|
||||
/>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import Content, {
|
||||
frontmatter,
|
||||
} from "../../../../../content/marketing/library/keyword-gap-analysis.mdx";
|
||||
import { LibrarySpokePage } from "@/components/library-page";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { COMPETITIVE_ANALYSIS_LIBRARY } from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library/competitive-analysis/keyword-gap-analysis";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_marketing/library/competitive-analysis/keyword-gap-analysis",
|
||||
)({
|
||||
head: () =>
|
||||
buildPageSeo({
|
||||
title: "Keyword Gap Analysis: How to Run One That Works",
|
||||
description: frontmatter.description,
|
||||
path: PATH,
|
||||
titleSuffix: "OpenSEO Library",
|
||||
ogType: "article",
|
||||
}),
|
||||
component: () => (
|
||||
<LibrarySpokePage
|
||||
title={frontmatter.title}
|
||||
description={frontmatter.description}
|
||||
crumb="Keyword gap analysis: subtract the brand terms first"
|
||||
path={PATH}
|
||||
library={COMPETITIVE_ANALYSIS_LIBRARY}
|
||||
>
|
||||
<Content components={{ ...defaultMdxComponents }} />
|
||||
</LibrarySpokePage>
|
||||
),
|
||||
});
|
||||
@ -1,11 +1,17 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { buildPageSeo } from "@/lib/seo";
|
||||
import { keywordResearchStrategies } from "@/lib/strategy-libraries";
|
||||
import {
|
||||
competitiveAnalysisStrategies,
|
||||
keywordResearchStrategies,
|
||||
} from "@/lib/strategy-libraries";
|
||||
|
||||
const PATH = "/library";
|
||||
const description =
|
||||
"Browse practical SEO strategies for finding search demand, mapping intent, and planning pages.";
|
||||
const featuredStrategies = keywordResearchStrategies.slice(0, 4);
|
||||
"Browse practical SEO strategies for finding search demand, sizing up competitors, mapping intent, and planning pages.";
|
||||
const featuredStrategies = [
|
||||
...keywordResearchStrategies.slice(0, 2),
|
||||
...competitiveAnalysisStrategies.slice(0, 2),
|
||||
];
|
||||
|
||||
export const Route = createFileRoute("/_marketing/library/")({
|
||||
head: () =>
|
||||
@ -55,6 +61,23 @@ function StrategyLibraryIndexPage() {
|
||||
<span aria-hidden="true">→</span>
|
||||
</p>
|
||||
</a>
|
||||
<a
|
||||
href="/library/competitive-analysis"
|
||||
className="block rounded-lg border border-[var(--color-border-subtle)] bg-white p-6 transition-colors hover:border-neutral-900"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold tracking-tight text-neutral-950">
|
||||
Competitive Analysis
|
||||
</h3>
|
||||
<p className="mt-3 max-w-xl text-sm leading-6 text-[var(--color-brand-muted)]">
|
||||
Find out which domains really hold your search results, measure
|
||||
the keyword and link gap honestly, and decide what is worth
|
||||
taking.
|
||||
</p>
|
||||
<p className="mt-5 text-sm font-medium text-neutral-950">
|
||||
View all {competitiveAnalysisStrategies.length} strategies{" "}
|
||||
<span aria-hidden="true">→</span>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user