[object Object]

← back to Ventura Claw Leads

yolo tick 1: JSON-LD LocalBusiness + ItemList structured data

520e445ac651f834d287215a006c0b30a83f1072 · 2026-05-06 17:00:03 -0700 · Steve Abrams

views/public/business.ejs: emit LocalBusiness JSON-LD with name/url/description/
phone/email/sameAs/address/geo. Always type=LocalBusiness (parent type) rather
than mapping to vertical-specific subtypes (Restaurant/BeautySalon/Pet…) since
proper subtype mapping requires per-vertical schema.org alignment, while
LocalBusiness is the safe parent for all 8 verticals.

views/public/find.ejs: emit ItemList JSON-LD on the directory list page —
top-30 businesses as ListItem entries with rank/url/name. Search engines surface
this as 'rich list' in results.

Live on prod 2026-05-06: parsed cleanly, 30 items rendered for /find,
LocalBusiness with geo+address for /business/:slug.

Files touched

Diff

commit 520e445ac651f834d287215a006c0b30a83f1072
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 17:00:03 2026 -0700

    yolo tick 1: JSON-LD LocalBusiness + ItemList structured data
    
    views/public/business.ejs: emit LocalBusiness JSON-LD with name/url/description/
    phone/email/sameAs/address/geo. Always type=LocalBusiness (parent type) rather
    than mapping to vertical-specific subtypes (Restaurant/BeautySalon/Pet…) since
    proper subtype mapping requires per-vertical schema.org alignment, while
    LocalBusiness is the safe parent for all 8 verticals.
    
    views/public/find.ejs: emit ItemList JSON-LD on the directory list page —
    top-30 businesses as ListItem entries with rank/url/name. Search engines surface
    this as 'rich list' in results.
    
    Live on prod 2026-05-06: parsed cleanly, 30 items rendered for /find,
    LocalBusiness with geo+address for /business/:slug.
---
 views/public/business.ejs | 31 +++++++++++++++++++++++++++++++
 views/public/find.ejs     | 23 +++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/views/public/business.ejs b/views/public/business.ejs
index 95942cb..1768baa 100644
--- a/views/public/business.ejs
+++ b/views/public/business.ejs
@@ -9,7 +9,38 @@
   }
   var _site = _safeUrl(business.website);
   var _addressOneline = [business.street, business.neighborhood || business.city, business.state, business.zip].filter(Boolean).join(', ');
+
+  // JSON-LD LocalBusiness — Google search rich-results + voice-search surface.
+  // We always emit type=LocalBusiness rather than mapping to subtypes (Restaurant,
+  // BeautySalon, etc.) because subtype mapping requires careful per-vertical
+  // schema.org alignment; LocalBusiness is the safe parent for all 8 verticals.
+  var _publicUrl = (typeof publicUrl !== 'undefined' && publicUrl) ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com';
+  var _ldBusiness = {
+    '@context': 'https://schema.org',
+    '@type': 'LocalBusiness',
+    '@id': _publicUrl + '/business/' + business.slug,
+    name: business.business_name,
+    url: _publicUrl + '/business/' + business.slug
+  };
+  if (business.headline) _ldBusiness.description = business.headline;
+  if (business.phone) _ldBusiness.telephone = business.phone;
+  if (business.email) _ldBusiness.email = business.email;
+  if (_site) _ldBusiness.sameAs = [_site];
+  if (business.street || business.city) {
+    _ldBusiness.address = {
+      '@type': 'PostalAddress',
+      streetAddress: business.street || undefined,
+      addressLocality: business.city || undefined,
+      addressRegion: business.state || 'CA',
+      postalCode: business.zip || undefined,
+      addressCountry: 'US'
+    };
+  }
+  if (business.latitude && business.longitude) {
+    _ldBusiness.geo = { '@type': 'GeoCoordinates', latitude: business.latitude, longitude: business.longitude };
+  }
 %>
+<script type="application/ld+json"><%- JSON.stringify(_ldBusiness) %></script>
 
 <section class="profile-hero">
   <p class="kicker"><%= verticalMeta ? verticalMeta.icon + '  ' + verticalMeta.label : business.vertical %></p>
diff --git a/views/public/find.ejs b/views/public/find.ejs
index d16cad3..a0a3295 100644
--- a/views/public/find.ejs
+++ b/views/public/find.ejs
@@ -1,6 +1,29 @@
 <%- include('../partials/head', { title }) %>
 <%- include('../partials/header') %>
 
+<%
+  // JSON-LD ItemList — surfaces the result page as a structured list to
+  // search engines. Each business appears as a ListItem with rank + URL +
+  // name, so searchers see "1. Olive & Salt Trattoria · 2. Boulevard Bagels …"
+  // in rich-results carousels.
+  var _publicUrl = (typeof publicUrl !== 'undefined' && publicUrl) ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com';
+  var _ldList = {
+    '@context': 'https://schema.org',
+    '@type': 'ItemList',
+    itemListOrder: 'https://schema.org/ItemListOrderAscending',
+    numberOfItems: businesses.length,
+    itemListElement: businesses.slice(0, 30).map(function(b, i){
+      return {
+        '@type': 'ListItem',
+        position: i + 1,
+        url: _publicUrl + '/business/' + b.slug,
+        name: b.business_name
+      };
+    })
+  };
+%>
+<script type="application/ld+json"><%- JSON.stringify(_ldList) %></script>
+
 <section class="find-page">
   <p class="kicker">Find a business</p>
   <h1 class="display-sm">Search Ventura Blvd</h1>

← cc3cfef v0.2 #3: admin/claim flow + lead inbox + profile editor + bi  ·  back to Ventura Claw Leads  ·  yolo tick 2: /unsubscribe handler — RFC 8058 one-click + lan 9c3866d →