Reference
Local SEO
The engine auto-generates JSON-LD structured data —
LocalBusiness,AggregateRating,GeoCoordinates,serviceArea— from yourdata/site.yamland 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:
LocalBusinessschema with address and contact infoAggregateRatingfromdata/reviews.jsonGeoCoordinatesfromsite.address.lat/site.address.lngserviceAreafromsite.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
ratingin reviews must be a number (JSON-typed frontmatter handles this automatically —"rating": 5in JSON is already a number)typematters for schema — use the most specific matchingLocalBusinesssubtype