SERP Topic Gap Monitor avatar

SERP Topic Gap Monitor

Pricing

Pay per usage

Go to Apify Store
SERP Topic Gap Monitor

SERP Topic Gap Monitor

Find the topics your competitors rank for that your site is missing. This SEO content gap analyzer accepts pre-fetched SERP data, extracts topics from competitor pages, and returns a scored gap report for content planning and competitive analysis.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Joe Slade

Joe Slade

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Categories

Share

Find the topics your competitors rank for that your site is missing. This SEO content gap analyzer takes pre-fetched SERP data, extracts topics from competitor pages, and returns a scored gap report you can act on immediately.

Who is this for?

  • SEO strategists building data-driven content calendars
  • Content teams deciding what to write next based on competitive gaps
  • Agencies running repeatable competitor content analysis across client portfolios
  • Growth marketers prioritizing keyword clusters by competitive opportunity

What it does

Give it your domain and a set of search results. The Actor compares what your competitors rank for against what your site covers, then returns a ranked list of topic gaps -- the content your competitors publish that you have not.

You control the SERP data source. This Actor processes structured search results that you provide, rather than fetching them itself. That means predictable costs (no surprise API calls), full control over which keywords and markets you analyze, and compatibility with any SERP data source -- Google SERP API actors, your own scraping pipeline, or exported spreadsheets.

Use cases

  • SEO content planning -- Find topics your competitors rank for that you have not covered yet
  • Content gap analysis -- Identify which keyword clusters have the weakest coverage on your domain
  • Competitor content analysis -- See which competitor pages cover topics you are missing
  • Editorial prioritization -- Rank content gaps by severity to focus on the highest-impact topics first

How it works

The Actor runs a four-step deterministic pipeline. Identical input always produces identical output.

  1. Tokenize -- Page content (or title + snippet when no content is available) is lowercased, stripped of punctuation, and filtered through a standard English stopword list. Tokens under 3 characters are removed.
  2. Extract topics -- For each keyword's SERP results, tokens that appear on 2 or more competitor pages are identified as topics. Your target domain's pages are checked for coverage but do not count toward the competitor threshold.
  3. Detect gaps -- Topics are merged across keywords. Competitor URLs are deduplicated. If your domain covers a topic in any keyword's results, it is marked as covered.
  4. Score and rank -- Each gap receives a score: gapScore = uniqueCompetitorPages / totalUniqueCompetitorPages, clamped to [0, 1] and rounded to 2 decimal places. Gaps are sorted by score (highest first), then alphabetically by topic.

Input

FieldTypeRequiredDefaultDescription
targetDomainstringYes--Domain to evaluate (e.g., myblog.com). Matches the hostname and subdomains (e.g., www.myblog.com, blog.myblog.com).
serpResultsarrayYes--Pre-fetched SERP data. Each entry represents one keyword's search results. See Input format below.
localestringNoenLocale of the SERP data (e.g., en, de, ja). Stored in output metadata for downstream tracking. Set this when analyzing non-English markets so your reports stay labeled correctly.
countrystringNousCountry code for the SERP data (e.g., us, gb, de). Stored in output metadata for downstream tracking. Useful when you run gap analyses across multiple markets.
minGapScorenumberNo0Minimum gap score threshold (0--1). Gaps scoring below this are excluded from output. Raise this (e.g., 0.3) to filter out low-signal gaps and focus on topics where many competitors outrank you.

Input format

Each entry in serpResults represents one keyword's organic search results:

{
"keyword": "content marketing strategy",
"organicResults": [
{
"url": "https://competitor.com/guide",
"title": "Ultimate Content Marketing Guide",
"snippet": "Learn how to build a content marketing strategy...",
"pageContent": "Full extracted text of the page..."
}
]
}
  • url and title are required for each organic result
  • snippet is optional -- used as fallback text when pageContent is missing
  • pageContent is optional but strongly recommended -- without it, topic extraction relies only on titles and snippets, which limits accuracy

Example input

{
"targetDomain": "myblog.com",
"serpResults": [
{
"keyword": "content marketing strategy",
"organicResults": [
{
"url": "https://myblog.com/strategy",
"title": "Content Strategy Guide",
"pageContent": "content marketing strategy planning editorial calendar audience research"
},
{
"url": "https://competitor-a.com/guide",
"title": "Ultimate Content Marketing Guide",
"pageContent": "content marketing strategy seo optimization keyword research analytics tracking performance metrics"
},
{
"url": "https://competitor-b.com/tips",
"title": "Marketing Tips",
"pageContent": "content marketing strategy distribution channels social media analytics tracking engagement"
}
]
}
],
"minGapScore": 0.3
}

Output

The Actor stores its output in the default key-value store under the OUTPUT key as a single JSON object. Use this data to decide what content to create, which topics to expand, and where your competitors have the strongest coverage advantage.

Output fields

FieldTypeDescription
targetDomainstringThe domain that was analyzed
keywordsAnalyzednumberNumber of keywords processed
totalGapsnumberNumber of gaps in the output (after minGapScore filtering)
gapsarrayRanked list of topic gaps (see below)
serpSummaryarrayPer-keyword summary of SERP data processed
metaobjectProcessing metadata (locale, country, minGapScore)
notesarrayInformational notes (e.g., filter warnings)

Gap object

Each gap in the gaps array contains:

FieldTypeDescription
topicstringThe topic token (normalized, lowercase)
gapScorenumberCoverage ratio (0--1). Higher means more competitors cover this topic.
competitorCoveragenumberNumber of unique competitor pages covering this topic
competitorUrlsstring[]URLs of competitor pages covering this topic (sorted)
targetCoveragebooleanWhether your domain covers this topic in any keyword's results
relatedKeywordsstring[]Keywords where this topic appeared (sorted)

SERP summary object

Each entry in serpSummary contains:

FieldTypeDescription
keywordstringThe keyword analyzed
resultsFetchednumberTotal organic results provided for this keyword
pagesFetchednumberResults that included pageContent
targetDomainRanknumber | nullPosition of target domain in results (1-indexed), or null if not found

Example output

{
"targetDomain": "myblog.com",
"keywordsAnalyzed": 2,
"totalGaps": 3,
"gaps": [
{
"topic": "analytics",
"gapScore": 0.8,
"competitorCoverage": 4,
"competitorUrls": [
"https://competitor-a.com/guide",
"https://competitor-a.com/seo",
"https://competitor-b.com/tips",
"https://competitor-c.com/blog"
],
"targetCoverage": false,
"relatedKeywords": ["content marketing strategy", "seo keyword research"]
},
{
"topic": "tracking",
"gapScore": 0.8,
"competitorCoverage": 4,
"competitorUrls": [
"https://competitor-a.com/guide",
"https://competitor-a.com/seo",
"https://competitor-b.com/tips",
"https://competitor-c.com/blog"
],
"targetCoverage": false,
"relatedKeywords": ["content marketing strategy", "seo keyword research"]
},
{
"topic": "strategy",
"gapScore": 0.6,
"competitorCoverage": 3,
"competitorUrls": [
"https://competitor-a.com/guide",
"https://competitor-b.com/tips",
"https://competitor-c.com/blog"
],
"targetCoverage": true,
"relatedKeywords": ["content marketing strategy"]
}
],
"serpSummary": [
{
"keyword": "content marketing strategy",
"resultsFetched": 4,
"pagesFetched": 4,
"targetDomainRank": 1
},
{
"keyword": "seo keyword research",
"resultsFetched": 3,
"pagesFetched": 3,
"targetDomainRank": 3
}
],
"meta": {
"locale": "en",
"country": "us",
"minGapScore": 0
},
"notes": []
}

How to read the results

  • High gapScore + targetCoverage: false -- Strong signal to create content. Many competitors cover this topic and you do not.
  • High gapScore + targetCoverage: true -- You cover this topic, but competitors cover it more broadly. Consider expanding your content.
  • Low gapScore -- Only a few competitors mention this topic. Lower priority unless strategically important to your roadmap.
  • relatedKeywords -- Shows which search queries surface this gap. More keywords means more search visibility at stake.

Error handling

ErrorCauseFix
No input provided.Actor received no inputProvide input via the Apify console or API
Input must include "targetDomain" and a non-empty "serpResults" array.Missing or empty required fieldsEnsure targetDomain is a non-empty string and serpResults has at least one entry

Malformed URLs in organicResults are handled gracefully -- they are treated as competitor pages (not matched as target domain) and do not crash the Actor.

Running locally

# Install dependencies
npm install
# Set up input -- edit storage/key_value_stores/default/INPUT.json
# Then run:
npm run start:dev
# Run tests
npm test
# Build for production
npm run build

Architecture

This Actor is designed as a composable building block in a larger SEO pipeline. Each processing step is a pure function with no side effects.

serpResults --> tokenize --> extractTopics --> detectGaps --> scoreAndRank --> output

Why this matters for your workflow:

  • Composable -- Pair it with any SERP-fetching Actor upstream and any reporting tool downstream. No vendor lock-in on data sources.
  • Reusable data -- Fetch SERP data once, then run multiple gap analyses against different target domains without additional API costs.
  • Deterministic -- Same input always produces the same output. Safe to use in automated pipelines, scheduled runs, and CI/CD workflows.
  • Testable -- Pure-function internals mean you can validate behavior without network access.