Structured Data Health Report
Executive summary
High-level signal for how this site's structured data is performing against the recommended footprint for a blue-collar local service business.
Detected industry & schema-type strategy
A client may operate in one or many trades. We pick the most specific LocalBusiness subtype where possible, then represent additional industries through Service / OfferCatalog / serviceType / knowsAbout / additionalType.
- Confidence:
- High
- Evidence:
- Schema @type already declared as ["GeneralContractor","HomeAndConstructionBusiness"] in lib/schemaBuilders.js. Homepage H1 + meta title: "Kitchen, Bath, Deck & Home Remodeling in Apex, NC". biz.gbpPrimaryCategory + service category set centered on remodeling and outdoor residential construction. No HVAC / Plumbing / Electrical / Roofing signals anywhere on the site.
- Recommended primary @type:
- GeneralContractor (currently implemented — correct)
- Secondary notes:
- Specialties (decks, kitchens, baths, outdoor living, full-home renovation) are represented as Service[] under /services/:cat/:svc. Single-industry — no additional LocalBusiness subtypes recommended.
- Confidence:
- High
- Evidence:
- Title "Kitchen, Bath, Deck & Home Remodeling"; hero copy "premium residential construction, remodeling, and outdoor living solutions"; service categories include remodeling-focused services.
- Recommended primary @type:
- GeneralContractor (already covered by the primary type)
- Secondary notes:
- schema.org has no Remodeling @type. serviceType on each Service entity captures the remodeling specialty; OfferCatalog (Prompt 3 backlog) would surface this as a grouped service tree.
- Confidence:
- High
- Evidence:
- Hero references "custom decks"; category set includes outdoor-living scope; service detail pages cover deck construction.
- Recommended primary @type:
- HomeAndConstructionBusiness (already covered by the multi-type)
- Secondary notes:
- Surfaced via Service entities. Recommend grouping under an "Outdoor Living" OfferCatalog branch in Prompt 3.
- Industry-specific LocalBusiness subtype (e.g. HVACBusiness, Plumber, RoofingContractor)
- HomeAndConstructionBusiness
- LocalBusiness
- ProfessionalService (fallback only)
- LocalBusiness (final fallback)
Schema types tracked for this site
Every schema type relevant to a local-service business, grouped by role. Each row tracks availability, recommendation, implementation, render verification, and next action.
| Schema type | Status | Priority | Page types | Next action |
|---|---|---|---|---|
Organization Top-level business entity, linked from every page. Evidence: No standalone Organization @type emitted site-wide. Phase 15 (SEO-032) intentionally consolidated this into a single LocalBusiness root entity. BlogPosting publisher inlines an Organization on /blog/:slug — that is a per-document inline node, not a duplicate site-level root. Affected: /blog/:slug (inline inside BlogPosting publisher only) | Recommended | Info | All pages | No action — consolidation is intentional. LocalBusiness covers Knowledge Graph + local-pack eligibility. |
LocalBusiness Local-business core entity with address, hours, geo, contact info. Evidence: buildLocalBusiness() emits @type=["GeneralContractor","HomeAndConstructionBusiness"], @id="…/#localbusiness", with name, url, telephone, email, address, geo, logo/image (@id refs), hours, sameAs, areaServed, paymentAccepted, currenciesAccepted, foundingDate, founder. Implemented in code; rendered output not personally verified. Affected: / (full emission) · /about · /contact · /services* · /service-areas/:city · /faqs · /blog · /blog/:slug · /privacy-policy · /terms-and-conditions (all reference via @id) | Implemented | Critical | Home, Contact, About | Manual: confirm <script type="application/ld+json"> renders on / and parses clean via validator.schema.org. |
HomeAndConstructionBusiness LocalBusiness subtype suitable for home-service trades. Evidence: Present on the LocalBusiness @type array — multi-type emission per Phase 15. Affected: / (referenced site-wide via @id) | Implemented | High | Home, Contact | No action — implemented. |
Industry-specific LocalBusiness subtype e.g. HVACBusiness, Plumber, RoofingContractor — picked from detected industry. Evidence: GeneralContractor is the industry-specific subtype currently in use — matches detected primary industry. Affected: / (referenced site-wide via @id) | Implemented | High | Home, Contact | No action — GeneralContractor is the appropriate subtype. |
WebSite Site-wide WebSite entity with name, url, optional SearchAction. Evidence: buildWebSite() emits @id="…/#website" with name, url, publisher → LocalBusiness @id. No SearchAction (intentional — no internal search). Affected: / | Implemented | High | Home | No action — implemented. |
WebPage Per-page WebPage entity describing each URL. Evidence: WebPage entities emitted on /, /services/:cat/:svc, /service-areas/:city, /privacy-policy, /terms-and-conditions; subtypes cover the rest. Affected: All indexable public routes | Implemented | Medium | All pages | No action — implemented. |
AboutPage WebPage subtype for the About page. Evidence: buildAboutSchema() emits AboutPage with isPartOf + about + mainEntity references. Affected: /about | Implemented | Medium | About | No action — implemented. |
ContactPage WebPage subtype for the Contact page. Evidence: buildContactSchema() emits ContactPage with isPartOf + about + mainEntity → LocalBusiness @id (no re-emission, per SEO-033). Affected: /contact | Implemented | Medium | Contact | No action — implemented. |
CollectionPage For index pages (services index, service areas index, blog index). Evidence: CollectionPage emitted on /services, /services/:cat, /service-areas, /blog. Affected: /services · /services/:cat · /service-areas · /blog | Implemented | Medium | Services index, Service areas index, Blog index | Prompt 3: pair each CollectionPage with an ItemList (see ItemList row). |
BreadcrumbList Breadcrumb trail rendered on every non-home page. Evidence: buildBreadcrumbList() emits BreadcrumbList on every public route. Home includes a single-item breadcrumb; deeper pages include full trails. Affected: All public routes | Implemented | High | All pages except Home | No action — implemented. |
SiteNavigationElement Optional — primary nav as structured data. Evidence: Not emitted — main nav is plain <Link> elements. Optional and low SEO value for local-service sites. | Available | Low | Home | No action recommended. |
| Schema type | Status | Priority | Page types | Next action |
|---|---|---|---|---|
PostalAddress Street, locality, region, postal code, country. Evidence: buildAddress() emits PostalAddress nested in LocalBusiness with streetAddress, addressLocality, addressRegion, postalCode, addressCountry. Affected: / (LocalBusiness) | Implemented | Critical | — | No action — implemented. |
GeoCoordinates Latitude/longitude for the primary business location. Evidence: buildGeo() emits GeoCoordinates only when biz.geo has non-zero lat/lng. Runtime presence depends on whether values are populated. Affected: / (LocalBusiness) | Implemented | High | — | Verify biz.geo.latitude / biz.geo.longitude are populated in business data; otherwise mark as "Coordinates Missing". |
OpeningHoursSpecification Per-day hours; required for hours-eligible rich results. Evidence: buildOpeningHours() emits one OpeningHoursSpecification per non-closed day. Hours visibly rendered on /contact and footer (Mon–Sat 9AM–5PM). Affected: / (LocalBusiness) | Implemented | High | — | No action — implemented. |
ContactPoint Phone, contact type, areaServed. Evidence: No discrete ContactPoint entity. LocalBusiness carries telephone + email directly (Google accepts both), but no ContactPoint child with contactType="customer service" exists. | Recommended | High | — | Prompt 3: add a ContactPoint child to LocalBusiness (contactType, telephone, email, areaServed, availableLanguage="en") — improves Knowledge Graph completeness. |
sameAs / social profile links Array of canonical social/profile URLs. Evidence: sameAs emitted from biz.schemaDefaults.sameAs through clean() filter. Runtime population depends on the data file. Affected: / (LocalBusiness) | Implemented | Medium | — | Verify Facebook, Instagram, and Google Business Profile URLs are populated; any unknown → "Social Profile URL Here" placeholder for the client. |
areaServed Cities/regions served — surfaced site-wide and on service-area pages. Evidence: areaServed emitted as City[] on LocalBusiness from biz.serviceAreaCities; Service entities also carry per-service areaServed (City or City[]). Affected: / (LocalBusiness) · /services/:cat/:svc (Service) | Implemented | High | — | No action — implemented. |
serviceArea GeoShape / GeoCircle describing service radius. Evidence: No GeoCircle / GeoShape serviceArea emitted. biz.serviceRadiusMiles is rendered in UI but not surfaced into schema. | Recommended | Medium | — | Prompt 3: add serviceArea GeoCircle (geoMidpoint = biz.geo, geoRadius = serviceRadiusMiles × 1609 meters) to LocalBusiness. |
paymentAccepted Accepted payment methods. Evidence: paymentAccepted joined from biz.schemaDefaults.paymentAccepted on LocalBusiness; values visible on /about Business Details card. | Implemented | Low | — | No action — implemented. |
priceRange Indicative price range (e.g. "$$"). Evidence: Not emitted. Optional but recommended for LocalBusiness completeness — helps populate the local-pack card. | Recommended | Low | — | Prompt 3 (low priority): add priceRange (e.g. "$$") to LocalBusiness if a representative range exists. |
currenciesAccepted ISO currency code(s). Evidence: currenciesAccepted joined from biz.schemaDefaults.currenciesAccepted on LocalBusiness. | Implemented | Info | — | No action — implemented. |
foundingDate Year/date business was founded — supports E-E-A-T. Evidence: foundingDate emitted as String(biz.foundingYear) on LocalBusiness. Year-only granularity is schema-org valid. | Implemented | Low | — | No action — implemented. |
founder / owner (Person) Only include if owner is publicly visible on the site. Evidence: founder Person emitted with biz.ownerName. Owner is publicly named on About + Home (owner card with portrait and bio), so inclusion is appropriate. Affected: / (LocalBusiness)
| Implemented | Info | — | No action — implemented and visibly supported. |
logo Square/wordmark logo URL. Evidence: logo emitted as @id ref to "…/#logo" ImageObject anchor (300×227, live-probed). Header/footer use a 240px WebP for performance; schema references the canonical asset. | Implemented | High | — | No action — implemented. |
image Primary business image(s) — must be crawlable. Evidence: image emitted as @id ref to the logo anchor on LocalBusiness. Service / City / Category pages also pass page-specific Pexels images as primaryImageOfPage. | Implemented | Medium | — | No action — implemented. |
| Schema type | Status | Priority | Page types | Next action |
|---|---|---|---|---|
Service One per offered service. Evidence: buildServiceSchema() emits a Service entity with @id, name, description, url, provider (@id), areaServed, category, serviceType, image on every /services/:cat/:svc. Affected: /services/:cat/:svc | Implemented | Critical | Service pages, City × Service pages | No action — implemented. |
OfferCatalog Grouped offerings under hasOfferCatalog. Evidence: No hasOfferCatalog on LocalBusiness. Services are visibly listed on /services and /services/:cat but not surfaced as a graph. | Recommended | Medium | Home, Services index | Prompt 3: add hasOfferCatalog to LocalBusiness grouping categories → Service[]. Surfaces the full service tree to Google. |
Offer Individual offer line items inside an OfferCatalog. Evidence: No Offer entities — would require concrete pricing. | Available | Info | — | No action — defer until per-service pricing is published. |
FAQPage Only on pages where Q&A is visibly rendered (not templated CTAs). Evidence: FAQPage emission is intentionally GATED (SEO-007/SEO-016). /faqs and /services/:cat/:svc SUPPRESS FAQPage because faqSeeds are question-only (no authored answers). FAQPage IS emitted on /blog/:slug when post.faq_items contains real Q&A. Affected: /blog/:slug (conditional on post.faq_items)
| Implemented | High | FAQ page, Service pages (only if visible) | Manual review: if Profile Launch authors real answers to faqSeeds, the gate in buildFAQPageSchema() / ServiceFAQAccordion can be lifted to emit FAQPage on /faqs and service pages. |
Question Child of FAQPage. Evidence: Question entities emitted as children of FAQPage on blog posts with faq_items. Affected: /blog/:slug (conditional) | Implemented | High | — | No action — child of FAQPage. |
Answer Child of Question. Evidence: Answer entities emitted as acceptedAnswer of Question. Affected: /blog/:slug (conditional) | Implemented | High | — | No action — child of Question. |
BlogPosting Per blog article. Evidence: BlogPosting emitted on /blog/:slug with headline, description, url, datePublished, dateModified, image, author (Person or Organization), publisher (inline Organization), mainEntityOfPage; optional editor + reviewedBy when post fields present. Affected: /blog/:slug | Implemented | High | Blog post | Per-post audit: confirm post.date_published, post.date_modified, post.author_name populated. Missing values weaken Article rich-result eligibility. |
Article Alternative to BlogPosting for non-blog editorial. Evidence: Not used — BlogPosting (a subtype of Article) is used instead, which is the correct choice for editorial blog content. | Available | Info | — | No action — BlogPosting is the appropriate subtype. |
ImageObject Only where a real, crawlable image asset exists. Evidence: buildLogoImageObject() emits one ImageObject with @id="…/#logo" on the homepage (300×227, verified). Service / City / Category pages emit ImageObject for primaryImageOfPage when a Pexels image is resolved. Affected: / · /services/:cat · /services/:cat/:svc · /service-areas/:city
| Implemented | Medium | — | No action — implemented. |
VideoObject Only where real videos are embedded. Evidence: No videos rendered anywhere on the site — emission would be inappropriate.
| Needs Manual Review | Info | — | No action — emit only when real video content is added. |
Review Only if real reviews are visibly rendered on the site. Evidence: GoogleReviewsSection displays Google review snippets on /, /about, /contact via Places API, but NO per-review schema.org Review entities are emitted. Self-served Review markup is generally not Google review-rich-result-eligible.
| Needs Manual Review | Medium | — | No action recommended — display value remains via on-page rendering; rich-result eligibility is not improved by self-served Review markup. |
AggregateRating Site-level aggregate — manual review required. Evidence: components/GoogleReviewSchema.jsx injects a SELF-SERVED LocalBusiness AggregateRating into <head> on / and /about when reviewData is available (overallRating + totalReviews from Google Places API). Affected: / · /about
| Implemented | High | — | MANUAL REVIEW (policy decision): self-served AggregateRating on LocalBusiness is generally NOT eligible for Google star rich results. Truthful (sourced from live Places API) and harmless to schema.org validity, but do not expect star rich results. Decide: KEEP for completeness signal, or REMOVE to keep the graph lean. |
ItemList For listings: services, areas, blog posts, reviews. Evidence: No ItemList emitted on /services, /services/:cat, /service-areas, /blog. These pages render full visible lists, so ItemList would be schema-org-valid and would improve list-page structure. | Recommended | Medium | Services index, Service areas index, Blog index | Prompt 3: add ItemList to /services (categories), /services/:cat (services in category), /service-areas (cities), /blog (posts). |
What schema each page type should carry
Use these expectations as the target state during Prompt 2 (audit) and Prompt 3 (remediation).
- Organization
- LocalBusiness / HomeAndConstructionBusiness (and industry-specific subtype where possible)
- WebSite
- WebPage
- Services summary (only if visibly listed)
- areaServed (only if visibly listed / available)
- ContactPoint
- sameAs (only if real social links exist)
- FAQPage ONLY if FAQs are visibly on the home page
- AboutPage
- Organization / LocalBusiness reference
- owner/founder Person (only if publicly visible)
- BreadcrumbList
- CollectionPage
- ItemList
- Service or OfferCatalog
- BreadcrumbList
- Service
- WebPage
- BreadcrumbList
- provider
- serviceType
- areaServed
- FAQPage ONLY if FAQs are visibly on that specific page
- CollectionPage
- ItemList
- areaServed
- BreadcrumbList
- WebPage
- Service or LocalBusiness reference (where appropriate)
- areaServed for that city/area
- BreadcrumbList
- FAQPage only if visible on that page
- NEVER create fake addresses for service-area cities
- FAQPage (only if Q&A are visible)
- Question
- Answer
- BreadcrumbList
- CollectionPage or Blog
- ItemList
- BreadcrumbList
- BlogPosting or Article
- headline
- description
- author (if available)
- publisher
- datePublished / dateModified (if available)
- image (only if real, crawlable image exists)
- BreadcrumbList
- ContactPage
- LocalBusiness
- PostalAddress (only if visible)
- ContactPoint
- openingHours (if available)
- geo (if available)
- BreadcrumbList
Reviews & AggregateRating — handle with care
Reviews can be displayed for users while still being ineligible for Google review rich results. The two are independent — treat them separately.
- Review schema may only be marked Available / Recommended if real reviews are visibly displayed on the site.
- Never invent or generate reviews. Never invent aggregate ratings.
- AggregateRating defaults to "Needs Manual Review" until validated against visible, on-page reviews.
- Google reviews can be DISPLAYED on the site via Places API, but self-served LocalBusiness Review / AggregateRating markup is generally NOT eligible for Google review rich results — display value remains, rich-result value does not.
Business facts needed to complete schema
Schema cannot accurately describe a business until these facts are confirmed. Prompt 2 should attempt detection; anything still unknown flows to a client-info request.
| Field | Status | Notes |
|---|---|---|
| Business phone | Implemented | biz.primaryPhone + biz.formattedPhone present; rendered site-wide (header CTA, footer, contact page). |
| Business address | Implemented | biz.address.formattedAddress + structured fields drive PostalAddress; rendered on /contact, /about, footer. |
| Business hours | Implemented | biz.hours feeds OpeningHoursSpecification; visible on /contact and footer (Mon–Sat). |
| Business email | Implemented | biz.primaryEmail present; visible on /contact and footer. |
| Primary industry | Implemented | Detected: General Contractor / Residential Remodeling. Matches existing @type. |
| Secondary industries | Implemented | Specialties (decks, kitchen/bath, outdoor living) — represented via Service[] (not separate trades). |
| Service areas | Implemented | data.entities.cities populated; areaServed already emitted on LocalBusiness and per-Service. |
| Social links | Needs Manual Review | biz.schemaDefaults.sameAs is emitted with clean() filtering. Confirm Facebook/Instagram/GBP URLs are populated; any unknown surfaces as "Social Profile URL Here". |
| Logo | Implemented | biz.logoUrl → ImageObject @id="…/#logo" (300×227 verified). Header/footer now use a 240px WebP for performance; schema still references the canonical asset. |
| Owner / founder name | Implemented | biz.ownerName visibly rendered on About + Home owner card. founder Person emitted in LocalBusiness — appropriate since it is public. |
| License information | Needs Manual Review | biz.licenses iterated in footer + TrustBadges when set. Confirm license numbers are populated; placeholder if missing: "License Information Here". |
| Insurance information | Needs Manual Review | biz.insuranceInfo iterated in footer when set. Verify populated; placeholder if missing: "Insurance Information Here". |
| Warranty information | Needs Manual Review | biz.warranties + biz.serviceGuarantees iterated on /about and /. Verify populated; placeholder if missing: "Warranty Information Here". |
| Financing / payment info | Implemented | biz.schemaDefaults.paymentAccepted joined into LocalBusiness paymentAccepted. Visible on /about Business Details card. |
| FAQs (real, on-page) | Missing | faqSeeds are question-only — no authored answers. FAQPage emission is INTENTIONALLY suppressed on /faqs and service pages (SEO-007/SEO-016). FAQPage IS emitted on /blog/:slug when post.faq_items contains real Q&A. |
| Blog dates / authors | Needs Manual Review | BlogPosting schema reads post.date_published, post.date_modified, post.author_name. Confirm every published post has these populated; missing values weaken Article rich-result eligibility. |
| Page-specific service details | Implemented | Service entity carries name/description/url/provider/areaServed/category/serviceType/image. Rich per-service body content (problems solved, signs you need it, process, pricing factors, local insights) all rendered. |
| Google review visibility / source | Needs Manual Review | Google reviews rendered via Places API on /, /about, /contact (GoogleReviewsSection). GoogleReviewSchema.jsx ALSO emits a self-served LocalBusiness AggregateRating on / and /about — see Reviews Warning section for the policy decision. |
Team instructions
Profile Launch QA workflow for keeping this report accurate.
- 1Run Prompt 2 to audit / check current schema and update this page.
- 2Run Prompt 3 to fix missing schema and update this page again.
- 3Use validator.schema.org for schema.org structural validation.
- 4Use Google Rich Results Test separately for Google rich-result eligibility.
- 5Do NOT assume schema.org validity equals Google rich-result eligibility — they are independent checks.
Prompt 3 remediation backlog
Output of this audit pass — the ordered list of schema changes for the next prompt to implement. Nothing is fixed in Prompt 2.
| Priority | Action |
|---|---|
| High | Add discrete ContactPoint child entity to LocalBusiness (contactType="customer service", telephone, email, areaServed, availableLanguage="en"). |
| High | Add ItemList to listing pages: /services (categories), /services/:cat (services), /service-areas (cities), /blog (posts). |
| High | Policy decision: self-served LocalBusiness AggregateRating on / and /about — keep (truthful, sourced from live Google Places API) or remove (no star rich-result eligibility expected). |
| Medium | Add serviceArea GeoCircle to LocalBusiness using biz.geo + biz.serviceRadiusMiles (meters = miles × 1609). |
| Medium | Add hasOfferCatalog to LocalBusiness — group categories → Service[] for a complete service tree. |
| Medium | Per-post BlogPosting audit: confirm date_published, date_modified, author_name populated for every published post. |
| Medium | Verify biz.schemaDefaults.sameAs is populated (Facebook, Instagram, Google Business Profile). Unknown → request "Social Profile URL Here" from client. |
| Medium | Verify biz.licenses / biz.insuranceInfo / biz.warranties populated — request "License/Insurance/Warranty Information Here" from client if missing. |
| Low | Add priceRange (e.g. "$$") to LocalBusiness if a representative range exists. |
| Info | Decision: author real Q&A pairs for faqSeeds → lift FAQPage gate on /faqs and service pages. Otherwise leave the gate in place. |
