Illustrative case study

A fictional business. The figures are a modelled scenario, not a client result.

Vreden Makelaars · 22-office housing brokerage · Utrecht

How a 22-office Dutch brokerage got Google to finally see its React listings

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

Plan
Signal
Offices
22
Months
10

01 · The challenge

Beautiful façades. Dark windows.

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.

What Googlebot got
<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

Six findings from the render test, the crawl and fourteen days of server logs.

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.

Crawl

61%

Googlebot lost in the filters

Fourteen days of logs: 1.9M Googlebot hits, 61% on sort and price-filter permutations, 4% on listing detail pages.

Crawl

9%

Render requests that failed

The listings API returned 5xx to Googlebot's renderer during the nightly feed import, leaving half-drawn pages in the index.

Links

0

Links past page one

Pagination used onClick handlers without hrefs; for a crawler, page two of every search simply did not exist.

Schema

None

No structured data

Price, floor area, rooms and energy label lived in client-side JSON only. 22 offices shared one contact page.

Performance

410ms

INP in the red

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

  • Filter & sort URLs
  • JS & API files
  • Other pages
  • Listing pages

03 · The strategy

Four tactics, eight houses.

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.

  1. Tactic 1 · houses 1 & 2

    JavaScript rendering

    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.

  2. Tactic 2 · houses 3 & 4

    Log-file analysis

    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.

  3. Tactic 3 · houses 5 & 6

    Structured data

    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.

  4. Tactic 4 · houses 7 & 8

    Core Web Vitals

    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

Scrub the months. Watch the lights come on.

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

Server-rendered listings

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.

  • Detail pages render server-sideDue M2
  • Price and address in initial HTMLDue M2
  • Photos as real <img> with alt textDue M3
  • Office pages pre-renderedDue M3
  • Sold listings return 410, not a blank 200Due M4
  • Rendered HTML parity checks in CIDue M4

05 · Month by month

10 months, one block at a time.

  1. MONTH 13,150 idx

    Survey the street

    Crawl, render and log audit; URL Inspection on 400 sampled listings; a fix per house, an issue per window.

  2. MONTH 23,400 idx

    Server-render the details

    Listing detail template to SSR; pagination becomes real links; filter params out of crawl.

  3. MONTH 34,100 idx

    Stop the 5xx

    Feed import moved off peak, edge cache on the listing API, facets capped at one dimension.

  4. MONTH 45,000 idx

    Hubs and sold pages

    64 neighbourhood hubs live; sold listings answer 410; rendered-HTML parity checks in CI.

  5. MONTH 55,900 idx

    Listings describe themselves

    RealEstateListing schema on every property; breadcrumbs; sitemap lastmod from the feed.

  6. MONTH 66,700 idx

    Twenty-two front doors

    Office pages with RealEstateAgent schema, hours and current listings; map loads on demand.

  7. MONTH 77,400 idx

    Faster filters

    Filter state to transitions; regression alerts on parameter crawl spikes; branch entities linked.

  8. MONTH 88,000 idx

    Lighter galleries

    Virtualised result list; reserved aspect ratios; office profiles aligned with office pages.

  9. MONTH 98,300 idx

    Last scripts out

    Chat deferred until idle; AVIF heroes with responsive sizes across all templates.

  10. MONTH 108,500 idx

    Every window lit

    All templates pass Core Web Vitals; a monthly log review handed to the in-house team.

06 · Results

The street, lit.

Modelled scenario for a fictional business — not a client result. Rankings and traffic are never guaranteed; see our Terms.

Indexed listings

3,1008,500

0k3k6k9kM0M1M2M3M4M5M6M7M8M9M10

Organic sessions / month

88k196k

0k100k200k196kM0M2M4M6M8M10

INP

410ms160ms

GOODNEEDS WORK0200500410ms160msINP, 75th percentile, mobile field data (ms)

Where the listings rank now

Google NL, mobile, Utrecht location. — = not in the top 100 before the work.

QuerySearches / moBeforeMonth 10Change
huis kopen utrecht12,100346▲ 28
appartement te koop utrecht6,600414▲ 37
makelaar utrecht5,400185▲ 13
huis te koop amersfoort4,400528▲ 44
woning kopen zeist1,900473▲ 44
makelaar houten1,300222▲ 20
eengezinswoning te koop nieuwegein880—4New
bovenwoning lombok utrecht320—1New

Get a plan like this

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.

← All case studies