Analytics
Search performance
Google Search Console data for your site: what you rank for, which positions moved, and where a small change would pay.
What you see
Search performance reads
your Google Search Console property: the searches that actually
surfaced one of your pages in a Google result. "Query" on this screen is
Search Console's own word for a literal search string typed into Google, not
this product's tracked prompts against AI engines; the import that fills this
screen asks Search Console for exactly the date, query, page and
country dimensions it defines
(api/services/integrations/google/reconciliation.py#import_gsc_daily_for_project).
The hero number is clicks for the selected window, with impressions,
click-through rate, and average position stepped down beneath it
(api/routers/integrations.py#gsc_search_performance). Click-through rate is
the window's total clicks divided by its total impressions, not an average of
the underlying rows' own rates, and average position is weighted by
impressions the way Google itself aggregates it (#_ctr, #_round2). The
backend keeps that figure to two decimal places; the screen itself shows one,
everywhere a position appears on it
(frontend/src/lib/format.js#formatPos). Because a lower position number is the better ranking,
the opposite of clicks or impressions, the position card spells the direction
out in words rather than leaving a signed number to misread: "2.0 positions
better" means the average position fell by two
(frontend/src/routes/(app)/performance/search/+page.svelte#posShift,
frontend/src/lib/format.js#formatPosDelta).
A "Closest to page one" card beside the hero surfaces the top row of Quick
wins below: among the queries within striking distance of page one, the one
with the most impressions, not necessarily the one with the best position.
topWin reads quickWins[0], and quick_wins sorts its candidates by
impressions then clicks, using position only as the 4-to-20 admission filter
described next
(frontend/src/routes/(app)/performance/search/+page.svelte#topWin,
api/services/integrations/google/gsc_search_metrics.py#quick_wins). Leading
with impressions is a reasonable choice even so: it points at the near-miss
with the most search demand behind it, the one worth acting on first even
when a better-positioned query sits further down the list. The card links
out to Prompts → Search Console opportunities, a related but separately
computed list; see
Suggestions and opportunities
for its own rule rather than assuming it repeats this screen's.
Where your impressions rank
Every query with a measured position falls into exactly one of four bands, in
a fixed order, labelled on screen as "1-3", "4-10", "11-20" and "21+", based
on that query's own impressions-weighted average position for the window.
Because that average is a continuous, often fractional number, the labels'
low edges are not exact: the cutoffs are position 3 or better for the first
band, then better than 3 up to and including 10, then better than 10 up to
and including 20, then worse than 20, so a query averaging 3.4 lands in
"4-10", not "1-3"
(api/services/integrations/google/gsc_search_metrics.py#_BUCKETS,
#position_bucket). A query with no position on record is left out of every
band and out of the total the bands divide by, so the bar and the list beside
it show a share of the impressions that had a ranked position, not a share of
every impression in the window (#position_mix). The fourth band carries its
own label on screen: "Position 21+, effectively unseen."
Quick wins and movers
Quick wins lists queries with an average position from 4.0 to 20.0, both
ends inclusive, and at least 10 impressions in the window, ranked by
impressions then clicks, capped at 10 rows
(api/services/integrations/google/gsc_search_metrics.py#quick_wins, called
with that floor and cap in api/routers/integrations.py#gsc_search_performance).
That is a wider position band and a lighter bar than
Search Console opportunities,
which requires position 5 or better, at least 100 impressions, a match to a
tracked prompt, and that same prompt's AI-engine citation rate to sit below
20 percent over the same window
(api/services/integrations/google/reconciliation.py#_RECON_LOW_AI_VISIBILITY):
the two lists answer different questions over different populations, not the
same ranking under two names.
Movers diffs each query's clicks in the selected window against its clicks in
the window immediately before it (the two are adjacent, though not quite the
same width; see below for the exact day counts), keeping a query only
where one of the two windows reached 5 clicks, then splits the result into
gainers and losers, each capped at 8 rows
(#compute_movers, called with that floor and cap in #gsc_search_performance).
A query that fell to zero clicks still shows up as a loser rather than
disappearing, because the comparison is a diff across both windows, not a
filter on the current one.
The trend, and the explorer beneath it
The daily trend draws clicks and impressions on their own separate scales, so
it shows shape rather than one shared magnitude. It renders once the window
is wider than 3 days, which every preset (7, 28, or 90) and any manually set
?days= of 4 or more satisfies: the series is zero-filled for every calendar
day in the window whether or not that day has actually been imported yet, so
a property with a single imported day still draws a full-width chart, one
real point against the rest at zero
(frontend/src/routes/(app)/performance/search/+page.svelte#hasTrend,
api/services/integrations/google/gsc_search_metrics.py#build_daily_series).
Beneath it, three tabs, queries, pages, and query-to-page pairs, each list up
to 100 rows ranked by clicks, with that row's own impressions,
click-through rate, and average position alongside it. The one Change column
compares clicks against the prior window; the other figures carry no delta
(#gscRows, api/routers/integrations.py#gsc_search_performance).
The window, and how it catches up with Google
Left alone, this screen opens on the last 28 days. You can switch it to 7,
28, or 90 days, or set ?days= yourself higher still; 180 is the ceiling
this screen enforces regardless of what the URL asks for
(frontend/src/routes/(app)/performance/search/+page.server.js#windowDays).
Every change figure, the hero's clicks, each explorer row, and the queries
feeding Movers, compares that window against the window immediately before
it: the two are adjacent, with no gap and no overlap, but not quite the same
width. The current window runs from window_days days ago through today,
both ends inclusive, which is window_days + 1 days; the prior window covers
exactly window_days days, ending the moment the current one starts. At the
default 28-day setting that is 29 days of current data against 28 days of
prior data, one day wider; the same window_days + 1 is why the trend chart
above draws 29 daily buckets for that same default, not 28
(api/routers/integrations.py#gsc_search_performance).
The import behind this screen does not treat Google's reporting lag as a
special case: on every scheduled run it re-pulls the last 7 days plus today,
8 calendar days inclusive, and overwrites what is already stored, so a day
that first lands incomplete gets corrected on a later run instead of needing
separate lag-handling logic. The first time a property is mapped, the import
instead pulls 181 days, from 180 days ago through today, both ends
inclusive, in one run split into 26 chunks of up to 7 days each rather than
a single request
(api/services/integrations/google/reconciliation.py#import_gsc_daily_for_project,
#_IMPORT_WINDOW_DAYS, #_BACKFILL_WINDOW_DAYS, #_BACKFILL_CHUNK_DAYS,
#_backfill_windows). See
Troubleshooting: Performance is empty
for exactly when the recurring import runs and what a window that lands
before your most recent import means for this screen.
Before you see data
Search performance needs Google Search Console connected and a property
mapped to this project. With no connection, the screen prompts you to connect
one; connected with nothing mapped, it prompts you to map a property
instead; mapped with no impressions recorded yet for the selected window, it
says so and points at the mapping to check, rather than showing an empty
chart
(frontend/src/routes/(app)/performance/+layout.server.js#gscConnected,
frontend/src/routes/(app)/performance/search/+page.server.js#gscMapped,
frontend/src/routes/(app)/performance/search/+page.svelte#gscHasData).
Related
- Suggestions and opportunities: the differently computed Search Console list Quick wins links out to
- Google rankings versus AI citations: why ranking well here does not by itself mean an AI engine cites you
- Troubleshooting: what an empty or stale-looking screen usually means
Last verified 2026-08-10