# Programmatic local SEO: score city pages before you publish

> How to avoid doorway pages in programmatic local SEO: which local data counts, free datasets, a row-scoring script, a publish cutoff and when to merge or skip.

Maciej Wiatr, 2026-11-07. https://maciejwiatr.com/blog/programmatic-local-seo-city-page-data

Programmatic local SEO projects usually fail because of the data, even when the template gets the blame. If the only field that changes between 10,000 rows is the city name, Google's spam policy already has a name for the result: doorway pages. This post covers which local data really differs by place, where to get it for free, how to score each city row, where to set a publish cutoff, and when not to build city pages at all.

## How does programmatic local SEO work?

You build one template and fill it from a database, with one row per location or per "service × city" pair. Each row targets a long-tail query such as "water heater repair in Katy." [Backlinko's guide](https://backlinko.com/programmatic-seo) and agency pages like [CodeWCG's](https://codewcg.com/resources/what-is-programmatic-seo/) describe the same mechanics: conditional sections, tables, internal links and a staged rollout.

The agency framing is where it goes wrong. CodeWCG treats scale as arithmetic (40 cities × 15 services = 600 pages) and gives no rule for which rows to drop. That rule is the whole job. A programmatic local SEO template is only as good as the rows you refuse to publish.

## Are programmatic location pages considered doorway pages by Google?

Not by default. They become doorways when the pages are near-identical and exist mainly to rank. Google's [spam policies](https://developers.google.com/search/docs/essentials/spam-policies) define doorway abuse as "when sites or pages are created to rank for specific, similar search queries." Two of the listed examples describe a lazy city template exactly:

- "Having multiple domain names or pages targeted at specific regions or cities that funnel users to one page"
- "Creating substantially similar pages that are closer to search results than a clearly defined, browseable hierarchy"

The same page covers scaled content abuse, which applies "no matter how it's created," including "automated transformations like synonymizing" and "stitching or combining content… without adding value."

John Mueller was direct about this in 2019. Asked about a plan for 1,300 city landing pages, he [replied](https://www.seroundtable.com/google-city-landing-pages-doorway-pages-28670.html): "That sounds like doorway pages, not something I'd recommend." You may see him credited with suggesting "15 to 20 areas" instead. [Patrick Stox](https://patrickstox.com/programmatic-seo/risks/doorway-pages/) repeats it, but the SERoundtable report of the tweet contains no such number, so I wouldn't plan around it.

Google's [2015 doorway post](https://developers.google.com/search/blog/2015/03/an-update-on-doorway-pages) still gives the best self-test. Two of its questions matter most for city pages, and most ranking articles skip them:

1. Does the page "duplicate useful aggregations of items (locations, products, etc.)"?
2. Is it "difficult or impossible to navigate to from other parts of your site"?

The second question means orphan city pages reachable only from a sitemap look worse than the same pages inside a real state, county and city hierarchy. Google also warned that "sites with large and well-established doorway campaigns might see a broad impact," so the risk applies to the whole site and isn't limited to the thin pages.

## What local data makes a city page unique instead of a city-name swap?

The useful test is whether the data would change the reader's decision. Stox's "delete-the-modifier test" is the quickest check: remove the city name from the page, and if it reads as generic, it's thin.

The large programmatic sites that hold up all have per-place data. Backlinko's estimates (Semrush figures, and Semrush owns Backlinko) put Zillow at about 243M organic visits a month from location, listing and property pages. Tripadvisor's "Things to do in [city]" pages rank for about 100K keywords. Each has listings, reviews or prices that differ by place.

Data that passes the modifier test, roughly in order of strength:

- **Your own data in that city.** Jobs done, staff, addresses, response times.
- **Real inventory or listing counts and prices.** How many providers, listings or products, and what they cost there.
- **Reviews and user content** tied to the location.
- **Public data that affects the decision.** Local wages, rents, housing and demographics, climate.

Population and a paragraph of history about the town don't count. Those fields differ by place, but they rarely change what anyone does, and every competitor can pull the same numbers.

## Where can I get free local datasets for location pages?

These are the free public sources the research found. Only the Census API was checked against official documentation. The others come from a [secondary roundup](https://homeowner.pages.dev/blog/best-free-government-apis-local-seo-content), so confirm coverage on bls.gov, ncei.noaa.gov and huduser.gov before you build on them.

| Source | What it gives | Geography | Access |
|---|---|---|---|
| Census Data API (ACS 1-year and 5-year) | Demographics, housing, income | Place (cities, towns, villages) and county | Free key, recommended |
| BLS OEWS | Wages by occupation, released each May | Every metro area | Key optional |
| NOAA NCEI Climate Data Online | 30-year climate normals | About 9,800 US stations | Free token |
| HUD User | Fair Market Rents, income limits | Check on huduser.gov | Free |

The [Census API user guide](https://www.census.gov/data/developers/guidance/api-user-guide.html) supports place-level geography, so a clause like `for=place:*&in=state:06` returns every place in California in one request. I couldn't confirm the daily query limit for keyless use, so get a key before you run it across thousands of rows. I'd treat these datasets as supporting fields. They make a page more useful, and on their own they rarely make one worth indexing. More on sourcing and joining fields is in the [data hub](https://maciejwiatr.com/programmatic-seo/data).

## How many data points per location does a page need before it is worth indexing?

Google sets no number, and Stox notes that "adding words does not fix a doorway." Backlinko's 500 to 1,000+ word advice doesn't change that, because a longer page with only the city name changed is still a doorway.

The only first-hand benchmark in the research comes from [theStacc's 512-page case study](https://thestacc.com/blog/programmatic-seo-case-study/). Its rule was at least 3 unique data points per row, and rows with fewer were never generated, because "pages with only one or two unique data points read as thin to Google's crawler." That's one company's rule, but it's a reasonable starting cutoff to test.

Scoring rows is simple if you define "unique" strictly: a field counts only when it has a real value for that place and isn't a regional fallback or national default.

```python
FIELDS = ["listing_count", "median_price", "review_count",
          "own_jobs_count", "metro_wage", "fair_market_rent"]

def unique_points(row, fallbacks):
    """fallbacks: values copied from a parent region or national default."""
    score = 0
    for f in FIELDS:
        value = row.get(f)
        if value in (None, "", 0):
            continue
        if value == fallbacks.get(f):
            continue  # inherited, not local
        score += 1
    return score

def publish_decision(row, fallbacks, has_demand, cutoff=3):
    score = unique_points(row, fallbacks)
    if score >= cutoff and has_demand:
        return "index"
    if row.get("region_id"):
        return "merge_into_region"
    return "do_not_generate"
```

Two choices in this code are my opinion. First, a fallback value never counts. Second, metro-level fields like BLS wages are shared by every city in the metro, so they don't separate sibling pages from each other. If you score them, they shouldn't be what pushes a row over the cutoff.

theStacc's results show what the gate bought. Its 198 city pages reached 89% indexation within 90 days, 34% got to page 1, and they produced 48% of attributed revenue. Across its portfolio, the top 10% of pages took 42% of clicks and the bottom 30% took 5%. So even well-gated pages produce uneven results. theStacc also capped publishing at 30 pages a week.

## Should low-data cities be noindexed, merged into a region page, or never generated?

It depends on whether the page exists yet and whether nearby rows share data. The flowchart shows the decision per row.

```mermaid
flowchart TD
    A[City row] --> B[Score unique fields]
    B --> C{Three or more?}
    C -->|Yes, with demand| D[Publish and index]
    C -->|No| E{Neighbors share data?}
    E -->|Yes| F[Merge into region page]
    E -->|No| G{Page already live?}
    G -->|No| H[Never generate]
    G -->|Yes| I[Noindex, crawl allowed]
```

The rules behind it:

- **No demand and no distinct data:** don't generate the page. This is the cheapest option and what theStacc did.
- **Nearby cities share the same data:** merge them into a regional or "areas served" page that lists them, and link it from the hierarchy.
- **Already live and thin:** noindex it, and don't block it in robots.txt. Mueller's older advice, covered by [SERoundtable](https://www.seroundtable.com/google-noindex-rel-canonical-confusion-26079.html), is that Google can't see a noindex on a page it can't crawl.
- **Redirect only to an equivalent page.** A city URL redirected to a generic homepage helps nobody.

Mueller's comments on this date from 2015 to 2017 and I found no recent restatement. The one line worth remembering is that Google's quality team would "rather you made the pages good quality or didn't have them at all." I walk through the noindex rollout itself in [how to noindex programmatic pages with Search Console data](https://maciejwiatr.com/blog/noindex-programmatic-pages-search-console-audit).

## When should you skip programmatic local pages entirely?

When the business has no real presence in those cities. Google's own local product sets a limit here. A [Business Profile](https://support.google.com/business/answer/9157481) can list up to 20 service areas, and Google suggests keeping the whole area within about 2 hours' drive of the business base. A plumber with one van generating pages for 800 cities is claiming far more reach than Google's own local product accepts.

I'd also skip it when every row would score below the cutoff, or when your only planned data is public datasets that competitors can pull just as easily. In both cases a small set of strong regional pages will do more than thousands of weak ones.

The risk isn't hypothetical. [Search Engine Journal reported](https://www.searchenginejournal.com/why-website-deindexed-by-google-for-programmatic-seo-bounced-back/552179/) that Tailride published 50,000 AI-generated long-tail pages and saw them deindexed with no manual action. Founder Miquel Palet's verdict: "probably not because AI. It was thin content and probably duplicated." His fix was a new domain, redirects and "Less pages + more quality." If you're already in that position, see [prune, merge or rebuild](https://maciejwiatr.com/blog/programmatic-seo-deindexing-recovery).

## Do programmatic local pages still work after Google's 2026 core and spam updates?

The [Search Status Dashboard](https://status.search.google.com/products/rGHU1u87FJnkP6W2GwMi/history) lists six ranking updates so far in 2026:

![Google Search Status Dashboard table of 2026 ranking updates, including the May and March core updates, with start dates and durations.](https://maciejwiatr.com/assets/blog/programmatic-local-seo-city-page-data/shot-1.webp "Use each update's start date and rollout duration from Google's official list to set the before and after windows when you compare Search Console data for each template (status.search.google.com, Nov 2026)")

| Update | Dates |
|---|---|
| March 2026 spam update | Mar 24 to 25 |
| March 2026 core update | Mar 27 to Apr 8 |
| May 2026 core update | May 21 to Jun 1 |
| June 2026 spam update | Jun 24 to 26 |
| August 2026 spam update | Aug 18 to 20 |
| September 2026 spam update | Sep 24 to Oct 7 |

Google hasn't said programmatic pages were targeted as a class. Claims of 40 to 90% drops after the May core update, like [1clickreport's](https://www.1clickreport.com/blog/google-may-2026-core-update-programmatic-seo-dead), trace back to forum posts. Secondary write-ups say near-identical "only the noun changes" pages were the group at risk, but none of them show data. That claim is plausible, because it matches what the spam policy already says.

Spam updates also arrive without a manual action notice, and [recovery can take months](https://dev.to/kevinlearynet/googles-august-2026-spam-update-2102), often waiting for a later update. The practical check is to compare Search Console clicks and indexed counts per template before and after each rollout window above.

My recommendation: score every city row before you generate it, publish only rows with at least 3 truly local fields and real demand, merge the rest into region pages, and build the state, county and city hierarchy so every page can be reached by browsing. If your data can't clear that bar in most cities, build a few strong regional pages instead.
