Skip to main content
WEB

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.

THE PROBLEM

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.

THE APPROACH

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.

City data to indexed page
City data to indexed page
HIGHLIGHTS

How it works

Blocks render only when the city has the data

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

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

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.

RESULTS

What it does now

1,340 indexable pagesone block registry composes every one
thin pages noindex themselvesand leave the sitemap in the same step
30 checks in one commandtypes, lint, SEO and content contracts
STACK

What it is built on

Next.jsapp router, static pages
TypeScriptblock registries and gates
Tailwind CSSdesign tokens and layout
Playwright and axeend-to-end and accessibility
Vitestunit and composer tests
GitHub Actionsgates, image build, deploy

Got something in your workflow that looks like this?