This is an implementation specification, not a wireframe. It defines what content must exist, the data structures beneath it, and how quality is enforced at build time.
It contains no page layouts and it is not a client-facing document — if you are looking for what the site will look like, this is the wrong artifact.
v3 was judged an 85th–90th percentile architecture that could not be built as written, because it deferred five load-bearing decisions.
v4 makes those five decisions and writes them as a spec an engineer can implement without asking a follow-up question.
Everything in this document either resolves a deferred decision or states a rule that a template must enforce. Later rounds added conversion surfaces on top of this architecture without changing it: the installation estimator, capped equipment pages, specials and the review counter (annex section M), and the conversion and trust modules in annex section N.
| Document | What it holds | Plain text |
|---|---|---|
| Wireframe v4 this page |
The architecture. Sitemap, the service × city intersection decision, page wireframes, build sequence. | /wireframe-v4.md |
| Implementation Annex | The build spec, sections A–L. CMS validation, slug registry, JSON-LD graph, CI gates, AI retrieval, off-site authority and measurement. | /annex.md |
| AI Visibility Runbook | The human protocols behind section L. GBP cadence, review velocity, NAP audit, the monthly citation panel, named owners. | /ai-visibility.md |
| Operational Runbook | Deployment and maintenance. Non-production mirrors, quarterly schema drift review, license regex ownership, per-city pre-launch checks. | /runbook.md |
The most durable SEO strategy for a home service business — HVAC, electrical, plumbing, roofing, or any trade that sends a van — is to be demonstrably the most trustworthy choice for the homeowner, and to make that demonstrable in a form a machine can read.
That is the whole argument. Everything downstream is mechanism.
Search systems evaluating a local business are trying to establish three things: that it is real, that it is local, and that it is competent. This architecture proves all three by requiring documented jobs with photographs and locations, named technicians with verifiable credentials, a licence number checked against the issuing state's database, and reviews from customers who exist. A site meeting those requirements does not merely rank well. It deserves to, because it represents a business that actually does the work.
It is an implementation specification: content model, validation rules, schema, and the build gates that enforce them. An engineer can build from it.
It is not a wireframe, a visual design, or a client-facing pitch. It contains no page layouts. Earlier versions were titled "wireframe" and that was simply wrong.
Scope: United States home-service SMBs. Licence patterns, NAP conventions and regulatory assumptions throughout are US-specific. Internationalisation is out of scope and is not gestured at.
This system produces empty templates when nobody documents the work.
Page publication is gated on evidence. A city page needs three documented jobs in that market. A service-and-city page needs one. A technician profile needs a job attached to that technician. When those do not exist, the templates render blocks saying so, in plain language, rather than filling the space with stock photography and adjectives.
That is a deliberate design decision and it is the single largest risk to the whole approach. It is not a CMS problem or a developer problem. It is an operational workflow the business has to run:
If that does not happen, the architecture will be correct and the site will be thin. No amount of engineering downstream fixes it, and this document would rather say so on page one than bury it.
No, and the distinction matters. Structuring a licence number, a project photograph or a technician's credentials makes existing, real trust legible to a machine. Google's objective is to identify the most trustworthy local expert; the objective here is to prove that is this business. A crawler that cannot parse the evidence is a customer who never sees it. Nothing here asks anyone to manufacture a signal they have not earned.
It would, without a gate. That is why there is one. Pages do not publish until a documented job exists for that service in that town, with photographs and a named technician. The theoretical maximum is cities multiplied by services; the actual count is capped by how much work the business can document. Ten jobs a month is ten pages a month, and the spec says cut the count, never the floor.
Because it is the only defence against commoditisation a competitor cannot copy by buying the same template. Attributing a specific job to a specific technician in a specific neighbourhood produces something checkable, which matters disproportionately in a category where a bad decision costs a homeowner real money and occasionally their safety.
For some businesses, yes, and that is a legitimate answer. A five-page site works until it needs to capture intent it has no page for, or expand into a new area. Worth saying plainly: a competitor with 140,000 reviews and fifteen years of links will outrank a structurally better site. Structure does not beat authority. It makes every review, link and job you do earn count for more.
Where are the colours, fonts and layouts? Deliberately absent. This is brand-agnostic by design, so the same architecture serves an electrician in Texas and a roofer in Florida. Visual identity is applied on top during design.
llms.txt has no documented adoption by any major retrieval system. Not sold as a ranking factor; the build only warns when it is missing.speakable has only ever been documented for news publishers. Included as cheap positioning and expected to do nothing.dateModified freshness is real for traditional crawlers and a forcing function against content rot. Its effect on AI retrieval is assumed, not demonstrated.llms.txt, which trades concrete harm for speculative benefit.
This specification is in its fourth major version. Every correction below was made because a review found something wrong, and each is marked in place in the document rather than quietly absorbed. A document that cannot show its own corrections is asking to be taken on faith.
| Corrected | From | To | Because |
|---|---|---|---|
| Project schema type | HowTo with performer | Article with author | HowTo has no performer property, so the reference silently dropped; Google retired HowTo rich results |
| Location node id | #local | #localbusiness | The architecture doc and the annex disagreed; a developer reading only one would emit unresolvable references |
| Person to Project link | performerIn | author on the Article | performerIn expects an Event, not a record of work |
| Service slugs | /heat-pumps/, /maintenance-plans/ | singular throughout | The annex locked a singular rule the sitemap then violated |
| Business age field | yearsInBusiness | yearEstablished | A count is wrong the day after it is typed, and nothing detects the drift |
| Zero-click event | sg_click | search_bounceback | It measured a return to search, not a zero-click visit. The name claimed something the metric could not see |
| Speakable target | LocalBusiness, Service | the page's WebPage node | Speakable has never been documented for those two types |
| Licence numbers | free text | verified against the issuing state's register | A licence number is a legal claim, not copy |
llms.txt. A concrete harm traded for a speculative benefit, on a file convention with no documented adoption.| Deferred decision in v3 | v4 resolution | Where |
|---|---|---|
| Service × location intersection Was blocking the whole build |
Model A adopted. Dedicated intersection pages at /[service]/[city]/, self-canonical, with a content floor and one local project each. Location hubs become indexes, not competitors. |
Section 1 |
| Internal linking existed as documentation only | Moved into the CMS as required reference fields. Links are generated from relationships, not typed by hand. Orphan detection runs nightly. | Section 3 |
| Schema listed as a stack of types | Rewritten as one entity graph joined by @id, so the site describes a company, its people, and its jobs rather than a pile of unconnected markup. |
Section 4 |
| Featured Local Projects assumed content capacity nobody had | A capture-to-publish workflow with a moderation gate, a named owner for every step, and an honest volume rule if the pipeline underdelivers. | Section 5 |
| Problem pages depended on a click that AI Overviews now absorb | Conversion paths that survive zero-click, plus the diagnostic tool returned to urgent framing on problem pages. | Section 6 |
ac repair phoenix is the query that pays. v3 published /ac-repair/ and /service-areas/phoenix/ and never said which one is supposed to rank for it. That ambiguity is how two pages end up competing for one query and neither wins.
URL convention, one convention only: /ac-repair/phoenix/. Service first, city second.
Home › AC Repair › Phoenix, which matches how the page is actually reached and how the entity graph nests./service-areas/phoenix/ links to every service intersection in Phoenix, carries the LocalBusiness entity, NAP, hours, and map, and holds the city's project feed. It does not try to rank for ac repair phoenix.One /service-areas/phoenix/ page carrying every service query for that city. Viable only at 1,500+ words per city with unique H2s, FAQs, and projects per service section.
Why it lost: it asks for more content per city than Model A does, while giving one URL to a dozen different money queries. The dilution is real and the internal linking logic never resolves cleanly. If the content team can write the mass Model B needs, they can write Model A's pages instead and get separate rankings for the effort.
?city=phoenix is not an architecture./ac-repair/, city-agnostic.Linking rules written in a document decay within months, because the person who wrote them stops being the person who publishes. Rules written as required CMS fields cannot decay, because a page will not save without them.
| Content type | Required reference fields | What the template generates from them |
|---|---|---|
| ServicePillar | city[] (areas served) |
An ItemList of every intersection page for this service. A block of every ProblemPage that references this pillar. A link to the matching comparison page. |
| Intersection | service (required, one)city (required, one)project (required, min 1) |
Breadcrumb, the up-link to its pillar, the across-link to its location hub, the project card with technician attribution, and its full JSON-LD block. |
| LocationHub | city (required, one) |
An index of every intersection in that city, the city's project feed, NAP and hours from the LocalBusiness record, GBP link. |
| ProblemPage | parentService (required, exactly one) |
A contextual link to the parent pillar using the exact service term, the pre-filled diagnostic widget, and the emergency CTA. |
| Project | technician, service, city, photos[], gps (all required) |
Appends itself to the matching intersection page, location hub feed, and technician profile. No manual linking, ever. |
| Technician | credentials[] |
Profile page, the list of every project performed, and the Person node in the graph. |
| From → To | Anchor |
|---|---|
| Pillar → intersection | Exact: AC repair in Phoenix |
| Intersection → problem | Symptom-rich partial: AC blowing warm air |
| Problem → pillar | Exact service term: AC repair |
| Intersection → location hub | City name: Phoenix service area |
| Project → technician | Technician name |
A cron job walks the published set and logs any page with fewer than 3 inbound internal links. The list goes to the SEO lead as an automated alert, not a dashboard somebody has to remember to open.
Orphans are the failure this architecture is most likely to produce, because the page count grows faster than anyone's attention. Catching them on a schedule is the only version of this that works.
v3 listed schema types. Types alone describe nothing. What earns entity understanding is the @id references between them: this company employs this person, who performed this job, which was this service, at this location.
// One graph, joined by @id. Every page emits its slice of it. Organization @id: /#org ├─ sameAs ──────→ GBP profile, BBB, social profiles ├─ employs ─────→ Person @id: /team/mike-rodriguez/#person └─ hasPart ─────→ LocalBusiness @id: /service-areas/phoenix/#localbusiness ├─ areaServed ──→ City (Phoenix) ├─ sameAs ──────→ GBP CID for that location └─ makesOffer ──→ Service @id: /ac-repair/#service Person @id: /team/mike-rodriguez/#person ├─ worksFor ────→ /#org ├─ hasCredential → EducationalOccupationalCredential (NATE) // the Person-to-Project link is `author` on the Project node below, not performerIn: // that property expects an Event, not a record of work. Project (typed Article) @id: /projects/phx-4412/#project ├─ location ────→ /service-areas/phoenix/#localbusiness ├─ about ───────→ /ac-repair/#service └─ author ──────→ /team/mike-rodriguez/#person Service @id: /ac-repair/#service └─ provider ────→ /service-areas/phoenix/#localbusiness (the city being served)
Every location page gets its own LocalBusiness with a unique @id, its own NAP, geo, openingHours, and sameAs to that location's GBP CID. Not one shared business entity repeated.
FAQPage applies only to a discrete Q&A accordion block, never to a whole page because it happens to answer questions. Comparison pages use Article plus HowTo or ItemList for the decision framework, and add FAQPage over their 3 to 5 genuine Q&A pairs. Symptom pages get no FAQ markup at all.
A TL;DR summary block sits at the top of problem and comparison pages. If an AI Overview is going to quote something, it should be the sentence you chose. The optional SpeakableSpecification markup goes on the page’s WebPage node, with an honest caveat: Google has only ever documented speakable for news publishers, so treat it as cheap positioning rather than a ranking feature, and never sell it to a client as one. Same for HowTo, which no longer produces rich results.
Project node with status Needs copy.@id to the JSON-LD graph automatically.| Role | Owns |
|---|---|
| Field technicians | Photo capture and job data, on site. Nothing else. |
| Content coordinator | Daily moderation, AI draft, chasing the fact-check, publishing. |
| SEO lead | Schema QA, the internal-link audit, Core Web Vitals monitoring, the quarterly decay crawl. |
| CSR / dispatcher | Google Business Profile Q&A inside a 24-hour SLA, and the weekly GBP post. |
Path A assumes the technician captures on site. That is the strongest evidence and the hardest habit to start. Most agency clients already do something simpler: they tell you about the job. A call, a text thread, a weekly catch-up. The work is being documented; it is just being documented to you instead of into a system.
| Already happening | The ask on top |
|---|---|
| Client describes the job | Job address or nearest cross street, and the date |
| Mentions who did it | The technician's name, spelled the same way every time |
| Sometimes sends a photo | Two photos, original files, from the phone that took them |
A shared Google Drive folder is the practical answer to the EXIF trap. Drive stores the original file; chat apps do not. One folder per client, the crew drops photos in, and a watcher on the folder creates the draft.
Crew drops photos in Drive → watcher reads EXIF (GPS + timestamp) → derives city, matches to service area → creates a Project draft → the existing briefing fills the narrative → moderation queue, then publish
How photos must reach the folder, because the method decides whether any of this works:
GPS in EXIF, work order reference, technician approval. Unlocks everything: tier-1 eligibility, the full project claim, the technician byline.
Client-briefed job with address, date, named technician and photos without verified GPS, confirmed in writing by the client. Publishes the page and counts toward the 3-project minimum. Does not count toward tier-1.
Symptom pages target exactly the queries AI Overviews synthesize and answer in place. Plan for the click not to happen, and make the page worth publishing anyway.
search_bounceback event when the referrer carries a search query and viewport time stays under 7 seconds. Naming correction: this is not zero-click. A true zero-click visitor never reaches the site at all and cannot be measured from it. This measures someone who arrived, did not find the answer fast enough, and went back — still real demand worth remarketing to, and still worth fixing the page for.v3 repositioned the diagnostic tool toward a low-intent check-up framing. That moves the tool away from the exact context where it converts.
Same tool, two entry framings, chosen by page type in the template.
author; there is no valid inverse to emit.These are not recommendations. The template blocks publishing when they are unmet.
tel: link with contactPoint schema.| Page type | Ships only when all of these are true |
|---|---|
| Homepage | Organization schema with sameAs to GBP, BBB, and social · speakable summary block · persistent phone in header · Lighthouse mobile performance 80+ |
| Service pillar | Service schema with areaServed array · links to every city intersection · FAQ markup only where genuine Q&A exists · link to the relevant comparison page |
| Intersection | 350–500 words unique to service × city · minimum 1 Featured Local Project with technician attribution · self-canonical · LocalBusiness @id reference · 3+ links to related problem pages |
| Location hub | LocalBusiness schema with unique @id, NAP, geo, openingHours · sameAs to that location's GBP CID · index of every intersection in the city · at least one project in the feed |
| Problem page | Article schema with hasPart → HowTo · pre-filled diagnostic widget · emergency CTA above fold · exact-anchor link to parent pillar · speakable TL;DR |
| Technician profile | Person schema with worksFor and hasCredential · relationship to every project performed · a bio with demonstrable experience, not adjectives |
Ordered by dependency and gate, not by calendar. Each phase ends at a gate; the gate is what earns the next phase, and a gate that fails means the phase repeats rather than the schedule slipping.
Intersection convention /[service]/[city]/ committed. City list committed. Canonical policy documented. Nothing is built yet, because every later phase encodes these choices and changing them afterward is a redirect project.
Build the relationships from Section 3 as enforced fields. Schema emits from those relationships. The auto-link blocks are generated, not authored.
@id resolving.Core Web Vitals guardrails, meta defaults, canonical behavior, auto-link blocks, and the two diagnostic-tool framings wired per page type.
This phase is not about the pages. It is about whether technicians actually document jobs on site. Run the full capture-moderate-approve-publish loop with real crews on real jobs.
Order by revenue per job, descending. Each sprint ends with an internal-link audit and a Core Web Vitals regression check.
A quarterly audit against Search Console. Any page down more than 30% in clicks year over year gets flagged for refresh. At this size, pages start dying quietly and nobody notices without the job running.
| Content | Refresh trigger |
|---|---|
| Service pillars | Annual review, or whenever the service scope itself changes. |
| Problem pages | Each major equipment generation, or when Search Console shows decay. |
| Intersection pages | A new project replaces the featured one as soon as a better job comes in from that city. |
| Location hubs | Quarterly project refresh at minimum. |
| Comparison content | When efficiency standards, rebate programs, or pricing economics move materially. |
| Technician profiles | When certifications change, and immediately when a technician leaves. A byline on a departed employee's project stays, with the profile marked accordingly. |