The finding that changed the engagement
Their domain did not serve a website. It issued a redirect to a subdomain owned by their booking software vendor.
Every link a customer had ever shared, every crawl, every bit of accumulated search equity from years of operating had been landing on an address the business did not own and could not take with them. If they ever changed software, all of it would vanish, and none of it had been building their own domain in the first place.
This is not exotic. It is the default configuration on several popular booking platforms, and almost nobody who signs up for one is told about it.
What the old site measured
Both sites measured in the same browser, cache-busted, same conditions, same day.
Measured before and after
| Before | After | |
|---|---|---|
| Title tag | "Home" — 4 chars | 43 chars |
| Meta description | none | 153 chars |
| Canonical tag | none | yes |
| h1 headings | 0 | 1 |
| Structured data types | 0 | 15 |
| Pages with FAQ schema | 0 | 32 |
| Total pages | 6 | 37 |
| Words on homepage | 241 | 778 |
| HTTP requests | 60 | 12 |
| Third-party requests | 41 across 5 hosts | 0 |
| Time to first byte | 288ms | 159ms |
| Audit score | 24 / 100 | 76 / 100 |
Some of what was on the old site was harder to put in a table. The brand name was misspelled in the hero. The page rendered the literal string undefinedundefinedundefined near the footer. An owner-operator with a handful of Google reviews was described as having "hundreds of clients" and "hundreds of reviews."
The contact form's consent line authorised the software vendor and its affiliates to send marketing, not the business. And the footer advertised the vendor by name, on the client's own site, to the client's own customers.
What we built
Thirty-seven pages: five service pages, twenty-one location pages, four long-form guides, plus the usual company pages and a twenty-question FAQ.
The twenty-one location pages were the part that needed care. Google's doorway-page policy demotes an entire domain for near-identical town pages, so each one had to earn its place with genuinely local content: real housing stock, real zip codes, questions specific to that area. Pages are generated from data models rather than hand-written, so nothing drifts out of sync and the whole site rebuilds from one command.
On the GEO side: fifteen distinct schema.org types joined into one entity graph by @id, FAQ schema on thirty-two pages, a sitemap generated from the filesystem so it cannot drift, and a robots.txt that names both the index-builder and the live-fetch agent for each AI vendor. Most sites that try this allow only one of the two and assume they are covered.
There is also a hand-written llms.txt telling answer engines what the business is, how pricing works, and explicitly that inventing a price would be wrong.
Five things that would have broken in production
All five were live or about to be. None announced itself. All were found by auditing after the build rather than trusting it.
1. Every booking would have failed. A database function still wrote an old status value that a newer constraint rejected. The booking form would have errored for every single customer.
2. The availability checker could double-book a cleaner. It searched for busy staff using that same dead status, so existing jobs were invisible to it and one hour could be offered to two customers.
3. Every appointment was stored four to five hours early. The database runs in UTC, the business runs in Cincinnati. A customer picking 9am got 9am UTC, which is 5am Eastern. The same bug sat in the admin's reschedule button, which moved every job four hours earlier each time it was pressed.
4. The admin was live on a public URL with the password changeme. The auth code fell back to a default written in the source, and the environment variable had never been set. Every customer address and phone number sat behind it.
5. A price calculator was being served on every page. Dead code, but /script.js contained the full pricing model, base rate through frequency discounts, on a site whose entire positioning is that it does not publish prices.
What the competition is doing
Same measurement, two national franchises' Cincinnati pages, both running paid ads to those pages.
Cincinnati market, measured same day
| Largest franchise | This build | |
|---|---|---|
| HTML payload | 841 KB | 23 KB |
| Third-party scripts | 5 | 0 |
| h1 headings | 0 | 1 |
| Schema types | 0 | 15 |
| Meta description | 14 chars | 153 chars |
Neither national franchise ships a single line of structured data on their Cincinnati page. That is the whole opportunity in local right now: the competition is large, well funded, and doing almost none of this.
Two things we would not do
Write reviews. We were asked for a set of invented five-star reviews with made-up names. We declined. The FTC's Rule on Consumer Reviews and Testimonials has been in force since October 2024 and carries per-violation penalties that land on the business owner, not the agency. Marking up invented reviews with AggregateRating is separately a Google policy violation that can strip rich results from an entire domain. We built the whole review system instead and left it empty until real Google reviews existed to transcribe. The rating markup is gated on real published reviews.
Publish prices, against the owner's wishes. That was his call and it was right for his business, but it left the site with nothing to say about the single most-asked question. Rather than cave or ignore it, the answer was a 1,800-word guide on what actually moves the price of a clean: condition, bathrooms, floor type, clutter, pets. No number printed. It is now the most citable page on the site.
What has not happened yet
The new site is not yet live on the production domain and has not taken a booking from a stranger. There is no lead count, no revenue figure and no ranking claim on this page because there is not yet an honest one to make.
Everything above is a measurement of the work itself. When the site is live and the numbers exist, they will be added here with the same before-and-after method, whatever they say.