Export your queries from the Search Console Performance report, keep the ones averaging roughly position 8 to 20 with enough impressions to trust, and classify why each is stuck: intent mismatch, the query only covered in passing, a competing page of your own, thin content, weak internal links or a weak title. Change one thing per page, log the date, and compare matched time windows.

Key takeaways

  • Position is an average, and not of one query. Google defines it as the topmost position of your link, averaged across all queries. A page's position blends many queries, so analyse query by query.
  • A short script finds the candidates. Filter an export to positions 8 to 20 with a minimum number of impressions, sorted by impressions.
  • Each stuck page has a specific cause. Six are common: wrong intent, query only covered in passing, a competing page of your own, thin content, weak internal links, a weak title.
  • Change one thing per page and write the date down. Two changes at once leave you unable to say which one worked.
  • Compare matched windows. Use the same number of days before and after, and judge impressions and position for that query, not the page's overall average.
  • This is a method, not a result. Nothing here promises movement, and no figure on this page comes from a measured case.

Step 1: pull the data, and know what position means

In Search Console, open Performance, then Search results. Pick a date range long enough to have volume (three months is a common choice), open the Queries tab with Clicks, Impressions, CTR and Position all switched on, and export it.

Before you read anything into the Position column, note how Google defines it: the topmost position occupied by a link to your site, averaged across all queries. A link must get an impression for its position to be recorded. That has one practical consequence. If you look at a page's overall position, you are looking at an average over every query it appeared for, which can hide a strong ranking for one query behind weak ones for others. Work from the Queries export, or filter to one page and then read its queries.

Step 2: filter to the candidates

You want queries close enough to page one to be worth the effort and with enough impressions that the average means something. This script reads the export and prints queries between positions 8 and 20 above an impressions threshold, busiest first (adjust the column names to match your export):

import csv, sys

def num(s):
    return float(str(s).replace('%', '').replace(',', '').strip() or 0)

rows = []
with open(sys.argv[1], encoding='utf-8-sig') as f:
    reader = csv.DictReader(f)
    label = reader.fieldnames[0]            # "Top queries" or "Top pages"
    for r in reader:
        rows.append((r[label], num(r['Clicks']), num(r['Impressions']),
                     num(r['CTR']), num(r['Position'])))

min_impressions = int(sys.argv[2]) if len(sys.argv) > 2 else 100
hits = [r for r in rows if 8 <= r[4] <= 20 and r[2] >= min_impressions]
for name, clicks, impr, ctr, pos in sorted(hits, key=lambda r: -r[2])[:30]:
    print(f'{impr:>7.0f} impressions  position {pos:>5.1f}  CTR {ctr:>4.1f}%  {name}')

Run it with the command below. The right threshold depends on your traffic: choose one that leaves you 20 to 30 rows. A position computed from a handful of impressions is noise, so a low threshold sends you after queries that were one lucky appearance. If you use the Search Console extension, you can ask for the same filter in plain language and then pull the queries for one page.

python3 candidates.py Queries.csv 100

Step 3: work out why each one is stuck

Open the page and the query side by side and put it in one of these six groups. The list is a working classification from practice, not a Google taxonomy.

Six common reasons a page sits around positions 8 to 20, and the first fix for each.
What you see Likely cause First fix
The query asks "how" or "why" and the page is a list or a pitchIntent mismatchRestructure to answer the question directly, or write the page that does
The query's words appear once, low on the pageCovered in passingAdd a section with the query as a heading and a short self-contained answer under it
Two of your own pages rank for the same queryCompeting with yourselfMerge into the stronger page and 301 the other, or differentiate their purposes
The ranking pages show data, tests or examples; yours is genericThin against the competitionAdd something original: a measurement, a worked example, a decision rule
The page has two or three inbound links, all from navigationWeak internal linksAdd contextual links from related pages, using anchor text that matches the query
Position 8 to 10 but a CTR far below the page's othersWeak title or snippetRewrite the title to answer the query more clearly

Two of these have guides here. Competing pages are the same duplicate problem covered in the Crawled, currently not indexed guide, and the script in the Discovered guide counts inbound links per page. A short, self-contained answer under a question heading is also what the AI citation guide identifies as the highest-leverage writing change, which helps in both places. For the rest of the checks, the SEO audit checklist covers titles, snippets and on-page basics. If speed is the suspect, see the INP guide and the layout-shift guide.

Step 4: one change per page, logged

Pick the single most likely cause for each page and fix only that. Then write it down:

A change log that takes two minutes per page.
Page Query Before (impressions, position, CTR) Change made Date
/your-page/the exact queryfrom the exportone sentenceYYYY-MM-DD

Two changes at once make the result unreadable: if the page moves, you cannot say which change did it, and if it does not, you cannot say which one to undo.

Step 5: measure fairly

Google's recrawl help says crawling can take anywhere from a few days to a few weeks, so a change shows up late and unevenly. A convention that works: wait 28 days, then compare the 28 days after the change with the 28 days before it, for that query and that page only. Judge impressions and position for the query, not the page's overall average, which other queries will drag around. Mark the result in the log and move on, whatever it shows. A single window is also noisy, so one good or bad month is a hint, not a verdict.

What to avoid

  • Chasing queries with a few impressions. Their position is an average of almost nothing.
  • Stuffing the query into the page. If the cause is intent or substance, repeating the words changes neither.
  • Rewriting everything at once. You lose the ability to learn from any single change.
  • Reading the page's overall position as a ranking. It is an average across every query the page appeared for.

What this cannot tell you

This is a procedure, not a measured case. It does not report results from any site and does not promise that a page will move. The six causes are a working classification, the 28-day window is a convention rather than a Google figure, and the positions 8 to 20 range is a convenient cut, not an official boundary.
Ranking but not clicked? If a page already ranks but few people click it, the problem is the snippet, not the position: find the outliers in your own data.

Badri Dutta

Software engineer · 8 years technical SEO

Written as the practical counterpart to the two indexing guides: once pages are indexed, the cheapest improvement is usually the one sitting just off page one. This is the routine for finding it and checking whether a change helped.

Full background →