# Home Service Website Architecture — Implementation Specification v4.2 Source: https://fc-build-spec-a5aaf279.vercel.app/ Publisher: Fortitude Creative. Last updated 2026-09-25. THE DOCUMENT SET - This document, the architecture: /wireframe-v4.md - Implementation Annex, the build spec: /annex.md (sections A-L) - AI Visibility Runbook: /ai-visibility.md (off-site protocols, citation panel) - Operational Runbook: /runbook.md (deployment and maintenance) - Both core documents in one fetch: /llms-full.txt - Everything condensed under 4,000 chars: /brief.txt 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 is not a client-facing document. v3 of this specification 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. --- ## Part 1. Why this exists ### 1.1 The premise 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. **The structure makes the evidence machine-readable. The evidence itself is for humans.** If those two ever conflict, the human wins, and several decisions in this document were made on exactly that basis. ### 1.2 What this document is, and is not 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. ### 1.3 The dependency this asks of the business, stated before anything technical **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: - A technician photographs before and after, on site, taking roughly two minutes - The office records the address and the job type - Someone reviews and publishes within 48 hours 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. ### 1.4 Questions a reviewer should ask, answered **Is this just optimising for Google?** 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 in this document asks anyone to manufacture a signal they have not earned. **Does a service-by-city page grid produce thin or duplicate content?** It would, without a gate. That is why there is one. Pages in the grid 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 page count is capped by how much work the business can document. A client producing ten jobs a month gets ten pages a month, and the specification says to cut the page count rather than the content floor. **Why insist on real project photographs instead of stock?** Because it is the only defence against commoditisation that a competitor cannot copy by buying the same template. Generic contractor sites use stock imagery and unverifiable claims. 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. **My competitor ranks fine with a five-page site. Is this over-engineered?** 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, demonstrate expertise in a town it has never written about, or expand into a new service area. It also works indefinitely for a business with overwhelming brand authority. This architecture is built for the case where you cannot outspend or outrank on reputation alone, and it wins the specific queries that convert best by matching intent with proof. 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. ### 1.5 What this document does not claim Several things here are honest bets rather than established mechanisms, and they are labelled as such where they appear: - **`llms.txt`** is cheap to ship and has no documented adoption by any major retrieval system. It is not sold as a ranking factor, and the build only warns when it is missing. - **`speakable` markup** has only ever been documented for news publishers. It is included as cheap positioning, targeted at the one node type where it is at least defined, and expected to do nothing. - **`dateModified` freshness** is a real signal for traditional crawlers and a useful forcing function against content rot. Its effect on AI retrieval is assumed, not demonstrated. - **Off-site authority dominates AI citation** for local commercial queries. Section L exists because the on-site work in sections A through K is necessary and insufficient, and pretending otherwise would be the most expensive kind of flattery. Three mechanisms proposed during review were rejected outright and the reasoning is recorded in place: a publish gate tied to Google review counts, which inverts cause and effect; automated demotion of requirements based on correlation, which is statistically undefined when every page implements every requirement; and failing production deploys over a missing `llms.txt`, which trades concrete harm for speculative benefit. A specification that cannot say what it is unsure about is not worth trusting on the parts it is sure about. --- ## Revision record 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. ### Corrections applied | 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` and `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 | ### Three mechanisms rejected Proposed during review, declined, with the reasoning kept in the document rather than the argument being erased: 1. **A publish gate tied to Google review count.** Blocking a new location's page until it has 20 reviews removes the page that generates reviews. Cause and effect inverted. 2. **Automated demotion of requirements by correlation.** Every published page implements every requirement by construction, so the independent variable has no variance and the correlation is undefined rather than merely weak. 3. **Failing production deploys over a missing `llms.txt`.** A concrete harm traded for a speculative benefit, on a file convention with no documented adoption. --- ## What changed from v3 | Deferred decision in v3 | v4 resolution | Where | |---|---|---| | **Service x 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. | Section 6 | --- ## 1. The service x location decision `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. ### Adopted: Model A, dedicated intersection pages **URL convention, one convention only:** `/ac-repair/phoenix/`. Service first, city second. - **Why service-first:** authority accumulates on the service pillar, which is the harder thing to rank. Breadcrumbs read `Home > AC Repair > Phoenix`, which matches how the page is actually reached and how the entity graph nests. - **Every intersection page is self-canonical.** No canonical pointing back to the pillar. A page that canonicals away cannot rank, which defeats the reason it exists. - **The location hub becomes an index.** `/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`. - **Content floor per intersection:** 350-500 words of copy unique to that service in that city, plus one Featured Local Project from that city for that service. A page that cannot meet the floor does not get published. An unpublished page costs nothing; a thin one costs the whole cluster. **The rule that keeps this from becoming a doorway farm:** page count is capped by project supply, not by ambition. Cities x services is a theoretical maximum, never a build target. Ship the intersections you can actually fill, in descending order of revenue per job. ### Rejected: Model B, multi-intent location hubs 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. ### Rules that follow from the decision - No query parameters for city. `?city=phoenix` is not an architecture. - No city name in the service pillar URL. The pillar is `/ac-repair/`, city-agnostic. - Suburbs get their own intersection only when there is a real job to show. Otherwise they are a section on the nearest metro intersection page. - One slug convention, frozen before the first page ships. Changing it later is a redirect project, not an edit. - Service pillar links down to every city intersection. Every intersection links up to its pillar and across to its location hub. --- ## 2. Site architecture ``` HOMEPAGE / | +-- TIER 1 - SERVICE PILLARS (city-agnostic) | /ac-repair/ /ac-installation/ /furnace-repair/ /furnace-installation/ | /heat-pump-repair/ /heat-pump-installation/ /maintenance-plan/ | /indoor-air-quality/ | +-- TIER 2 - INTERSECTIONS (the money pages) | /ac-repair/phoenix/ /ac-repair/scottsdale/ /ac-repair/mesa/ | /furnace-repair/phoenix/ | ...one per service x city that has a project to show | +-- TIER 2b - LOCATION HUBS (indexes + local entity) | NOTE: for a SERVICE-AREA business (one address, many towns) these become | service-area pages carrying the city's project feed, and the site emits ONE | LocalBusiness at the real address. See annex section P. | /service-areas/ /service-areas/phoenix/ /service-areas/scottsdale/ | +-- TIER 3 - PROBLEM / SYMPTOM PAGES (top of funnel, urgent) | /ac-blowing-warm-air/ /ac-not-turning-on/ /furnace-wont-ignite/ | /ac-making-loud-noise/ /thermostat-not-responding/ | ...each references exactly one parent pillar | +-- TIER 2c - CONVERSION SURFACES (v4.1, annex section M) | /ac-installation/estimate/ /furnace-installation/estimate/ | /heat-pump-installation/estimate/ (sizing + tier, never a price) | /specials/ (singleton; offers as anchored sections) | /brands/[brand]/[model]/ (capped: 20 total, 5 per brand, | each gated on a real installed job) | +-- TIER 3b - COMPARISON / DECISION CLUSTER | /repair-vs-replace/ /heat-pump-vs-furnace/ /ac-size-guide/ | /seer-rating-guide/ | +-- TIER 3c - EDITORIAL (annex section O; optional for a small team) | /resources/[slug]/ seasonal, rebate, local code, community | ...one required parent service pillar each, unique topicKey, 600-word floor | +-- TIER 4 - PEOPLE, PROOF, TRUST /team/[name]/ /projects/ /projects/[slug]/ /reviews/ /about/ /financing/ /brands/ ``` --- ## 3. CMS content model and enforced internal linking 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` (1), `city` (1), `project` (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` (1) | An index of every intersection in that city, the city's project feed, NAP and hours from the `LocalBusiness` record, GBP link. | | **ProblemPage** | `parentService` (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. | ### Anchor text rules, applied by template | 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 | ### Orphan detection (nightly job) 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. --- ## 4. The schema entity graph 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. ``` 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 expressed as `author` on the Project node below, # not as 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) ``` - **Per-location identity:** 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. - **FAQ markup discipline:** `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. See annex section K.3. - **Built for extraction:** 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, and comes 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: use it where the content genuinely has steps, not for the snippet it used to earn. Note: the implementation annex corrects the project node to `Article`. See annex section C. --- ## 5. Featured Local Projects: the operating procedure **This is the module most likely to fail, and the failure is operational, not structural.** Most contractors are not content studios. A workflow that needs two hours of technician time per project ships four projects instead of forty, and every page with a required project field stays unpublished. **Step 1 - Field capture.** Technicians capture in CompanyCam or Fulcrum using a structured form, on site, before leaving. Required: 2 before photos (one wide, one close), 2 after photos, what was wrong, what we did, the part or brand, and the neighborhood or landmark. GPS is auto-captured in EXIF. This is non-negotiable and it is the moderation gate. **Step 2 - Moderation queue.** The content coordinator clears the queue daily. Reject anything missing GPS or a signed work order, no exceptions, because the whole claim of local proof rests on it. Accepted jobs create a `Project` node with status *Needs copy*. **Step 3 - Ghostwriting with real attribution.** An AI draft is generated from the captured fields; the technician does not write prose. The draft goes to the named technician on mobile: *Is this correct? Yes / No plus comments.* The timestamped approval is what legitimizes the byline. Without it, the byline is a lie and the E-E-A-T signal is worth less than nothing. **Step 4 - Publish and link.** On approval, the CMS hook appends the project to its intersection page, the city feed, and the technician profile, and adds its `@id` to the JSON-LD graph automatically. Nobody links anything by hand. The relationship is the link. ### Who owns what | 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, internal-link audit, Core Web Vitals monitoring, quarterly decay crawl. | | CSR / dispatcher | Google Business Profile Q&A inside a 24-hour SLA, and the weekly GBP post. | ### Capture path B: the client briefs the agency Path A above assumes the technician captures on site in CompanyCam or Fulcrum. 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. Build on that habit rather than replacing it. The delta is small and specific: | 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, sent from the phone that took them** | **The EXIF trap, and it is the one that silently kills this.** WhatsApp, iMessage, Telegram, Slack and Facebook Messenger all strip location metadata and recompress on send. A photo that arrives through a chat thread has no GPS, so it fails the moderation gate while looking perfectly fine to the person who sent it. Either use an upload form that stamps geolocation at upload time, or ask for the original file through a link. Do not ask people to "send photos" through chat and then wonder why nothing passes. #### The Drive folder intake, and why it shrinks the ask 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. **This makes the ask on the crew smaller than it first appears.** If EXIF survives, the photo already carries the GPS coordinates and the timestamp — so nobody has to type an address or a date. The system derives the location and the day from the file, and the briefing that already happens supplies the only things a photo cannot know: which technician, what was wrong, what was done. 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:** - Upload the photo file from the camera roll through the Drive app. This preserves everything. - **Not** the Drive app's document scanner, which produces a flattened file with no camera metadata. - **Not** a screenshot of a photo, and **not** a photo re-shared out of a chat thread, which has already been stripped once. **Verify on day one with a single test photo, before assuming ninety days of capture.** If Location Services is turned off for the camera app on a crew phone, **every photo that phone ever takes will arrive without GPS**, and nothing in the workflow will announce it. That is a phone setting, not a process problem, and it is a two-minute fix if you find it on day one instead of day eighty-nine. The intake validates EXIF on arrival and replies when it is missing, so a silent failure becomes a visible one. **Two evidence grades, and the difference is stated rather than hidden:** - **Grade A** — GPS in EXIF, work order reference, technician approval. Unlocks everything: tier-1 eligibility, the full project claim, the technician byline. - **Grade B** — client-briefed job with address, date, named technician and photos without verified GPS, confirmed in writing by the client. Publishes the page, counts toward the 3-project minimum, and does **not** count toward tier-1 eligibility. **The byline rule holds in both grades.** If the agency writes the piece from a briefing, the byline does not claim a technician wrote or approved it unless that technician actually did. The page may say the work was performed by a named technician, because that is true. Attributing the *writing* to someone who never saw it is the one thing this whole evidence model exists to prevent. **The honest volume rule:** if the pipeline produces 10 projects a month, you get 10 project-backed intersection pages a month. Cut the page count, never the content floor. A hundred thin intersection pages is the doorway-page pattern that gets a site filtered; ten real ones is a moat competitors cannot copy without sending their own trucks out. --- ## 6. Problem pages and the zero-click problem 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. ### Required on every problem page - **Diagnostic tool, pre-populated** with the symptom the page is about. The visitor arrives mid-problem; the tool should already know which problem. - **Emergency CTA above the fold.** *Still not cooling? Schedule same-day service.* Phone number tappable, visible without scrolling. - **A secondary capture that is not a phone call** - a downloadable checklist that takes an email or mobile number, for the visitor who is troubleshooting but not ready to book. - **Remarketing pixel with search-bounceback detection:** fire a `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 — which is still real demand worth remarketing to, and still worth fixing the page for. ### Correction carried over from v3 v3 repositioned the diagnostic tool toward a low-intent *check-up* framing, which moves the tool away from the exact context where it converts. - **Problem pages:** urgent framing. Something is broken right now. The tool triages and hands off to a same-day booking. - **Homepage and maintenance pages:** check-up framing. Nothing is broken; the tool is a reason to engage. Same tool, two entry framings, chosen by page type in the template. --- ## 7. Page-level wireframes ### Intersection page - `/ac-repair/phoenix/` (new in v4) - H1: AC Repair in Phoenix, AZ - Above fold: phone (`tel:` link) + same-day booking CTA + trust row - Unique copy, 350-500 words: this service, in this city. Local conditions, response area, what the job usually involves here. - Featured Local Project, required, min 1: before/after photos, neighborhood, named technician, link to profile - Auto-generated links: up to the AC Repair pillar, across to the Phoenix service area, down to 3+ related problem pages - Reviews filtered to this city where volume allows - Schema: Service + LocalBusiness `@id` + BreadcrumbList + Project ### Service pillar - `/ac-repair/` - H1: AC Repair Services - What we fix: scope, process, what to expect, pricing approach without numbers - Cities we serve (ItemList): auto-generated link to every intersection page for this service - Common problems: auto-generated from every ProblemPage referencing this pillar - Decision support: link to `/repair-vs-replace/` - Schema: Service with `areaServed` array + FAQPage only if a real Q&A block exists ### Problem / symptom page - `/ac-blowing-warm-air/` - TL;DR summary block, speakable: the sentence you want quoted back by an AI Overview - Emergency CTA above fold: still not cooling? Same-day service. Tap to call. - Diagnostic tool, pre-filled, urgent framing - Causes ranked: what a homeowner can check, then what needs a technician - Secondary capture: downloadable checklist to email or SMS - Parent link: exact-match anchor to the AC Repair pillar - Schema: Article + `hasPart` HowTo + Speakable ### Location hub - `/service-areas/phoenix/` - H1: Plumbing Services in Phoenix, AZ - NAP + hours + map from the LocalBusiness record, unique `@id` - Index of services in this city: auto-generated links to every intersection - City project feed: every project performed in Phoenix, newest first - Technicians who cover this area (Person cards) - Schema: LocalBusiness `@id` + `sameAs` GBP CID + `geo` + `openingHours` ### Technician profile - `/team/mike-rodriguez/` - Photo, name, years in trade: a real person, photographed on a real job - Credentials: NATE, EPA, manufacturer certifications - Jobs performed: auto-populated from every approved Project - Schema: Person + `worksFor` + `hasCredential`. Projects link to the technician through `author` on the Project node; there is no valid inverse property to emit here. ### Comparison / decision page - `/repair-vs-replace/` - TL;DR verdict block, speakable: answer the question in the first screen - Decision framework: age, repair cost ratio, efficiency, refrigerant type - Comparison table, structured and extractable - Schema: Article + HowTo or ItemList. Not FAQPage for the whole page. --- ## 8. Technical defaults, enforced by the template These are not recommendations. The template blocks publishing when they are unmet. - **Meta description:** required field. Empty means the page cannot publish. - **Canonical:** self-referential by default, with a deliberate override only. - **Phone number:** above the fold on every template, as a `tel:` link with `contactPoint` schema. - **Hero image:** 100 KB or under, preloaded. - **Video:** lite-YouTube embed pattern. Never a raw iframe. - **Third-party widgets** (reviews, financing): lazy-loaded through IntersectionObserver. --- ## 9. Acceptance criteria by page type | 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 x 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 | --- ## 10. Build sequence 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. **Phase 1 - decisions frozen.** Slug conventions and canonical rules, written down and locked. 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. *Gate: one document, approved, that a developer could hand to a second developer without explanation.* **Phase 2 - the content model.** CMS types, required reference fields, schema generators, auto-linking. Build the relationships from Section 3 as enforced fields. *Gate: a test intersection page cannot be saved without a service, a city, and a project attached, and its JSON-LD graph validates with every `@id` resolving.* **Phase 3 - templates.** Page templates with the publish-blocking defaults from Section 8, CWV guardrails, and the two diagnostic-tool framings wired per page type. *Gate: a page with an empty meta description is rejected by the CMS, and mobile Lighthouse performance clears 80 on the heaviest template.* **Phase 4 - the pilot, and the real test.** One city, two services, end to end, including field capture. This phase is not about the pages. It is about whether technicians actually document jobs on site. *Gate: projects arrive from the field without being chased, and the median capture takes minutes. If it does not clear, fix the capture workflow before scaling anything, and re-cut the page count to match true throughput.* **Phase 5 - rollout in sprints.** Remaining intersections, location hubs, problem pages, ordered by revenue per job, descending. *Gate per sprint: zero orphans in the nightly report, no CWV regression, every new intersection carrying its project.* **Phase 6 - the decay cycle turns on.** Triggered once the published set passes roughly 100 pages. A quarterly audit against Search Console; any page down more than 30% in clicks year over year gets flagged for refresh. *Gate: the first decay crawl runs and produces a refresh queue with named owners.* --- ## 11. Content maintenance | 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. | --- Home Service Website Architecture, Implementation Specification v4.2. Supersedes v3. Fortitude Creative, September 2026.