Rendering
71%
Listings indexed as shells
Of 10,900 live listing URLs, 7,800 were either not indexed or indexed with no price, address or photo — the URL Inspection snapshot showed a spinner.
A fictional business. The figures are a modelled scenario, not a client result.
Vreden Makelaars · 22-office housing brokerage · Utrecht
A client-side rendered listings app meant most properties reached Google as empty shells. Over 10 months we fixed the street house by house — 8 fixes, 38 issues, one window each.
+174%
indexed listings
01 · The challenge
A client-side rendered listings app meant most properties reached Google as empty shells.
Vreden's React app looked superb to buyers: instant filters, a live map, swipeable galleries. But all of it arrived after the JavaScript ran. Googlebot fetched the HTML, queued the page for rendering, and — on a site with 10,900 live listings and a nightly feed that churned a tenth of them — often moved on before the queue came round. New listings took nine days to appear in search; many sold before they ever did.
<title>Vreden Makelaars</title> <body> <div id="root"></div> <script src="/app.4f2c.js"> </script> </body>
0 words · 0 links · 0 images
What buyers saw
Oudegracht 214-bis, Utrecht
€ 565.000 k.k. · 96 m² · 3 rooms · label A
02 · Diagnosis
Rendering
71%
Of 10,900 live listing URLs, 7,800 were either not indexed or indexed with no price, address or photo — the URL Inspection snapshot showed a spinner.
Crawl
61%
Fourteen days of logs: 1.9M Googlebot hits, 61% on sort and price-filter permutations, 4% on listing detail pages.
Crawl
9%
The listings API returned 5xx to Googlebot's renderer during the nightly feed import, leaving half-drawn pages in the index.
Links
0
Pagination used onClick handlers without hrefs; for a crawler, page two of every search simply did not exist.
Schema
None
Price, floor area, rooms and energy label lived in client-side JSON only. 22 offices shared one contact page.
Performance
410ms
Each filter tap re-rendered the map and the full result list. LCP 4.1s, CLS 0.31 from galleries without dimensions.
Googlebot requests, before
Googlebot requests, month 10
03 · The strategy
Built on our Signal technical SEO plan. Every fix was scoped as one house on the street, with its issues as windows — so the in-house developers could see, at a glance, what was still dark.
Tactic 1 · houses 1 & 2
Listing, office and neighbourhood templates rendered on the server, with streamed HTML that carries price, address, floor plan and photos before any JavaScript runs. Real anchor links replace click-handler pagination.
Tactic 2 · houses 3 & 4
Raw access logs, verified by reverse DNS, piped into a daily digest: crawl share per template, Googlebot 5xx per hour, time-to-first-crawl for new listings. Filters canonicalised and capped from that evidence.
Tactic 3 · houses 5 & 6
RealEstateListing with Offer, floor size, rooms, energy label and geo on every property; RealEstateAgent per office; breadcrumbs from city to street — generated from the same feed that drives the page and checked in CI.
Tactic 4 · houses 7 & 8
The map loads on interaction, filters run as non-blocking transitions, the result list is virtualised, hero photos ship as responsive AVIF in the first HTML with reserved space.
04 · The street
Pick a house to see what was wrong and how it was fixed. A lit door means every issue in that house is resolved.
Month
Before
Issues resolved
0 / 38
Indexed listings
3,100
INP (p75)
410ms
Before the engagement: 0 of 38 issues resolved, 3,100 indexed listings.
House 1 · JavaScript rendering
Found: Listing pages shipped an empty <div id="root"> and fetched price, address and photos after hydration. Googlebot's first wave indexed a shell.
Fixed: Listing and office pages moved to server rendering with streamed HTML; the client only hydrates the gallery and the viewing-request form.
05 · Month by month
MONTH 13,150 idx
Crawl, render and log audit; URL Inspection on 400 sampled listings; a fix per house, an issue per window.
MONTH 23,400 idx
Listing detail template to SSR; pagination becomes real links; filter params out of crawl.
MONTH 34,100 idx
Feed import moved off peak, edge cache on the listing API, facets capped at one dimension.
MONTH 45,000 idx
64 neighbourhood hubs live; sold listings answer 410; rendered-HTML parity checks in CI.
MONTH 55,900 idx
RealEstateListing schema on every property; breadcrumbs; sitemap lastmod from the feed.
MONTH 66,700 idx
Office pages with RealEstateAgent schema, hours and current listings; map loads on demand.
MONTH 77,400 idx
Filter state to transitions; regression alerts on parameter crawl spikes; branch entities linked.
MONTH 88,000 idx
Virtualised result list; reserved aspect ratios; office profiles aligned with office pages.
MONTH 98,300 idx
Chat deferred until idle; AVIF heroes with responsive sizes across all templates.
MONTH 108,500 idx
All templates pass Core Web Vitals; a monthly log review handed to the in-house team.
06 · Results
Modelled scenario for a fictional business — not a client result. Rankings and traffic are never guaranteed; see our Terms.
3,1008,500
88k196k
410ms160ms
Google NL, mobile, Utrecht location. — = not in the top 100 before the work.
| Query | Searches / mo | Before | Month 10 | Change |
|---|---|---|---|---|
| huis kopen utrecht | 12,100 | 34 | 6 | ▲ 28 |
| appartement te koop utrecht | 6,600 | 41 | 4 | ▲ 37 |
| makelaar utrecht | 5,400 | 18 | 5 | ▲ 13 |
| huis te koop amersfoort | 4,400 | 52 | 8 | ▲ 44 |
| woning kopen zeist | 1,900 | 47 | 3 | ▲ 44 |
| makelaar houten | 1,300 | 22 | 2 | ▲ 20 |
| eengezinswoning te koop nieuwegein | 880 | — | 4 | New |
| bovenwoning lombok utrecht | 320 | — | 1 | New |
If your listings, products or pages render in the browser, Google may be seeing empty shells too. We will tell you which windows are dark.