Measuring programmatic SEO: the five metrics that matter
Page-level reporting is useless on a generated site. A single page with forty impressions a month tells you nothing. The unit of analysis has to be the batch — pages sharing a template and a publication wave — and every metric below is defined at that level.
Define cohorts before you measure anything
A cohort is the intersection of a template and a publication wave. Two dimensions, both known at generation time and both worth storing alongside each page.
cohort_id = template_name + "_" + publication_wave
example: "alternatives_2026-W28"
Everything downstream — indexation, impressions, clicks — is aggregated by cohort. Without this key, a template regression introduced in week 30 is invisible inside sitewide averages.
Cohorts map directly onto sitemap segmentation. Isolating each wave in its own sitemap gives you coverage data per cohort for free — see sitemap segmentation.
The five metrics
1. Indexation rate
indexed_urls / submitted_urls per cohort, at 30, 60 and 90 days
The 30-day figure measures discovery. The 90-day figure measures perceived value. A cohort that is 60% indexed at 30 days and still 60% at 90 days has a content problem, not a crawl problem — the distinction is unpacked in crawl budget.
2. Impression concentration
share of cohort impressions held by the top 10% of pages
Above 80%, the batch is effectively ten good pages plus filler. That is the clearest quantitative signal that the quality gate was too permissive.
3. Query yield
distinct queries with impressions / indexed pages
A healthy programmatic page attracts three to fifteen distinct queries. Below one, pages are ranking for nothing beyond their exact target — usually a sign the content covers the query but not its neighbourhood.
4. Click depth distribution
Not available from Search Console; it comes from your own crawl. Track the share of the cohort at depth 1, 2, 3 and 4+. A cohort drifting deeper over time means the hubs are saturating and need splitting.
5. Position band movement
share of cohort pages in positions 1-3, 4-10, 11-20, 21+
Average position across a batch is a meaningless number — it mixes a page at 2 with a page at 80. Band distribution shows where the mass actually sits and whether it is moving.
Pulling it programmatically
The Search Console API returns performance data by page and query with a date range. Two constraints shape any implementation: results are capped per request and must be paginated, and data is filtered for privacy, so low-volume queries are omitted entirely.
request = {
"startDate": "2026-05-01",
"endDate": "2026-07-31",
"dimensions": ["page", "query"],
"rowLimit": 25000,
"startRow": 0
}
# paginate until fewer rows than rowLimit are returned
# then join `page` against your cohort table
The privacy filtering matters more than it looks on a long tail batch: a substantial share of impressions comes from queries too rare to be reported. Query-level totals will therefore always undercount page-level totals. Compare like with like.
Detecting template regressions
Every template change is a deployment that can break a thousand pages at once. Three checks run against the cohort after each change:
- Structural diff. Heading counts, word counts and internal link counts before and after. An unintended drop shows up immediately.
- Markup validity. Structured data parsed and required fields verified across a sample of the batch, per structured data validation.
- Impression slope. A cohort whose impressions diverge from its siblings within three weeks of a deployment points at that deployment.
A reporting cadence that survives contact with reality
- Weekly — indexation rate of the most recent cohort only. Everything else is noise at that horizon.
- Monthly — the five metrics, all live cohorts, plus regression checks against the last deployment.
- Quarterly — pruning decisions on cohorts older than six months, with removals pushed through IndexNow.
The quarterly review is the one that gets skipped, and it is the one that keeps the site's average quality from drifting down as batches accumulate.
Frequently asked questions
Why is average position misleading on a generated batch?
Because it averages a page at position 2 with a page at position 80 into a number that describes neither. Distribution across position bands is far more actionable.
Why do query totals not match page totals in Search Console?
Queries that are too rare are filtered out for privacy reasons. On a long tail batch these account for a substantial share of impressions, so query-level sums always undercount.
How soon after publishing should a cohort be judged?
Not before 30 days for discovery and not before 90 days for perceived value. Earlier readings on a young domain lead to reversing decisions that would have worked.