1,340 local pages, each one gated on real city data
A Dutch home-energy site where every page is composed from a block registry and leaves the index when its local data runs thin.
What was in the way
Programmatic SEO fails the same way every time. You generate a page per city, every page turns out to be one paragraph with the name swapped, and the search engine files the whole set as thin content. The cost is not one weak page, it is the domain.
Dutch consumer-protection rules close the usual escape route. A site quoting prices, subsidies and payback times cannot invent a figure or a rating to pad a template, so the copy has to come from data that actually exists per city.
How it was built
Each page is composed at build time from a registry of blocks. A block declares the zone it sits in, the ground it renders on, and a predicate over that city's own data, so a block with nothing local to say never renders and never leaves an empty band under a heading. Adding a city is data, not code.
A quality gate then scores the composed page on its local-only text and on how many local data hooks it carries. A page that fails renders a noindex shell, and the same predicate decides sitemap membership, so the sitemap cannot advertise a URL that tells the crawler to ignore it.

How it works

Blocks render only when the city has the data
Grid-congestion status, build-year mix and solar yield come from per-city sources. No data for a city means that block does not render there.
Duplicate copy measured with the city name removed
Two pages read as 79 percent unique on raw text and came back identical once every city name was normalised out. The normalised figure is the one the corpus check enforces.

No number renders without a source on disk
Price bands carry the date they were last checked, and ratings render only when the reviews signal is live. Nothing on the page can be filled in with a guess.

Accessibility fails the pull request
An axe scan at WCAG 2.2 AA runs against a production build on every PR, with the Lighthouse accessibility assertion set to error. The audience skews older, so contrast and keyboard paths are functional requirements.