Skip to main content
SiteShi p

Reference

Local SEO

The engine auto-generates JSON-LD structured data — LocalBusiness, AggregateRating, GeoCoordinates, serviceArea — from your data/site.yaml and reviews data.

The engine auto-generates all schema.org JSON-LD from structured data in data/ and site.yaml. You set the data — the engine handles the markup.

What the engine generates automatically:

  • LocalBusiness schema with address and contact info
  • AggregateRating from data/reviews.json
  • GeoCoordinates from site.address.lat / site.address.lng
  • serviceArea from site.serviceArea

1. Update data/site.yaml

Add these fields (merge with what's already there):

{
  "name": "Acme Plumbing",
  "type": "Plumber",
  "phone": "(512) 555-0100",
  "email": "hello@acmeplumbing.com",
  "address": {
    "street": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "zip": "78701",
    "lat": 30.2672,
    "lng": -97.7431
  },
  "serviceArea": ["Austin", "Round Rock", "Cedar Park", "Georgetown"]
}

type — use a Schema.org LocalBusiness subtype that matches the business: Plumber, Electrician, Restaurant, LegalService, MedicalClinic, RealEstateAgent, AutoRepair, HairSalon, DentalClinic, Accountant, Contractor, Landscaper — or use LocalBusiness if nothing fits.

lat / lng — get from Google Maps: right-click the location → "What's here?" → copy the coordinates.

2. Create data/reviews.json

[
  {
    "author": "Sarah M.",
    "rating": 5,
    "text": "Fantastic service — showed up on time and fixed the issue in under an hour.",
    "date": "2026-01-15"
  },
  {
    "author": "James R.",
    "rating": 5,
    "text": "Very professional and reasonably priced. Will use again.",
    "date": "2026-01-02"
  },
  {
    "author": "Linda K.",
    "rating": 4,
    "text": "Good work, friendly technician.",
    "date": "2025-12-18"
  }
]

Field requirements:

Field Type Notes
author string Display name
rating number 1–5
text string Review body
date string ISO format YYYY-MM-DD

The engine calculates the average rating and count automatically.

3. Display reviews on the page

The engine makes reviews available as {{ data.reviews }} in any template:

{% if data.reviews %}
<section class="py-16 bg-secondary-50">
  <div class="max-w-4xl mx-auto px-6">
    {% assign avgRating = 0 %}
    {% for r in data.reviews %}{% assign avgRating = avgRating | plus: r.rating %}{% endfor %}
    {% assign avgRating = avgRating | divided_by: data.reviews.size %}

    <div class="text-center mb-12">
      <div class="text-5xl font-bold">{{ avgRating }}</div>
      <div class="text-yellow-400 text-2xl my-2">★★★★★</div>
      <p class="text-secondary-500">{{ data.reviews | size }} reviews</p>
    </div>

    <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
      {% for review in data.reviews %}
      <div class="bg-white rounded-xl p-6 shadow-sm">
        <div class="text-yellow-400 mb-3">{% for i in (1..review.rating) %}★{% endfor %}</div>
        <p class="text-secondary-700 mb-4">"{{ review.text }}"</p>
        <div class="flex items-center justify-between">
          <span class="font-medium">{{ review.author }}</span>
          <time class="text-secondary-400 text-sm">{{ review.date | date: "%b %Y" }}</time>
        </div>
      </div>
      {% endfor %}
    </div>
  </div>
</section>
{% endif %}

4. Display service area (optional)

<section class="py-12">
  <h2 class="text-2xl font-bold mb-6">Areas We Serve</h2>
  <div class="flex flex-wrap gap-3">
    {% for area in site.serviceArea %}
    <span class="px-4 py-2 bg-primary-50 text-primary-700 rounded-full font-medium">{{ area }}</span>
    {% endfor %}
  </div>
</section>

Rules

  • rating in reviews must be a number (JSON-typed frontmatter handles this automatically — "rating": 5 in JSON is already a number)
  • type matters for schema — use the most specific matching LocalBusiness subtype

Found something out of date? Open an issue. ← All docs