Service comparison pages let salons dominate local search results — when they are built on structured data. AiSalonHub's comparison pages prove that a well-architected EmDash CMS page, backed by Cloudflare D1 and JSON-LD schema, can outrank generic salon directories and win Google's featured snippets.
The Problem — Local Salon SEO Is Brutal
Searching for "nail salon near me" or "gel manicure vs dip powder" returns a mixed bag of Yelp listings, Google Business Profiles, and generic salon aggregators. Independent salons struggle to break through because:
- **Duplicate content penalties** hit salons that copy-paste service descriptions from manufacturers
- **Thin pages** — a single paragraph per service without pricing, duration, or comparison data
- **Missing schema markup** — most salon sites lack LocalBusiness, Service, or FAQ structured data
- **No comparison context** — users want to know "Shellac vs Gel-X vs Dip Powder" but get individual service pages with no side-by-side data
A typical nail salon in a competitive metro like Seattle or Austin might have 30+ service pages but zero of them rank for long-tail comparison queries. The opportunity is obvious: build comparison pages that combine multiple services with structured data that Google can parse directly into rich results.
The Solution — AiSalonHub's Comparison Pages with Structured Data
AiSalonHub addresses this with dedicated service comparison pages — not just "Nail Services" as a flat list, but structured pages that explicitly compare services across dimensions: price, duration, durability, aftercare, and ideal customer. Each comparison page targets a specific user intent:
| Query Intent | Comparison Page | Target Services |
|---|---|---|
| Budget decision | Manicure Types Compared | Classic, Gel, Dip, Acrylic |
| Durability research | Long-Lasting Nail Services | Gel-X, Dip Powder, Acrylic, Polygel |
| Occasion planning | Wedding Nail Services | Shellac, Gel-X, Dip, Custom Art |
| Health-conscious | Non-Toxic Nail Services | Breathable Polish, Hema-Free Dip, Organic Spa |
Each page is a standalone entity in EmDash CMS with its own URL, meta tags, and — critically — its own structured data block. The pages are rendered from reusable schema definitions that AiSalonHub's CMS architecture treats as data-first objects.
Architecture — EmDash Schema with Repeater Fields for Services
The foundation of AiSalonHub's comparison engine is EmDash CMS's repeater field system. Rather than hardcoding each service into a template, the CMS stores services as repeatable schema fragments:
EmDash Repeater Field Schema (Simplified)
```json
{
"comparisonPage": {
"title": "Nail Service Comparison",
"slug": "manicure-types-compared",
"introParagraph": "Compare our most popular nail services by price, time, and longevity.",
"services": {
"_type": "repeater",
"fields": [
{
"serviceName": "Classic Manicure",
"slug": "classic-manicure",
"price": 25,
"duration": 30,
"durability": "3-5 days",
"aftercare": "Moisturize cuticles daily, avoid harsh chemicals",
"idealFor": "Low-maintenance, natural nail lovers",
"schemaType": "Service"
},
{
"serviceName": "Gel Manicure",
"slug": "gel-manicure",
"price": 45,
"duration": 45,
"durability": "2-3 weeks",
"aftercare": "Avoid peeling, use cuticle oil, no filing",
"idealFor": "Active lifestyles, chip-resistant polish desired",
"schemaType": "Service"
},
{
"serviceName": "Dip Powder",
"slug": "dip-powder",
"price": 55,
"duration": 60,
"durability": "3-4 weeks",
"aftercare": "Don't soak in acetone too long, refill every 2-3 weeks",
"idealFor": "Strong nails needed, long wear without UV lamp"
}
]
},
"faqs": {
"_type": "repeater",
"fields": [
{
"question": "Which nail service lasts the longest?",
"answer": "Dip powder typically lasts 3-4 weeks, slightly longer than gel manicures (2-3 weeks)."
},
{
"question": "Can I switch between service types?",
"answer": "Yes, but your nail technician may recommend a transition period to allow natural nails to adjust."
}
]
}
}
}
```
Cloudflare D1 Query Layer
When a comparison page loads, AiSalonHub queries Cloudflare D1 to dynamically pull service data. This means the comparison pages stay fresh without editorial intervention:
```sql
-- Example D1 query for a comparison page
SELECT
s.name AS service_name,
s.slug,
s.base_price,
s.duration_minutes,
s.durability_days,
s.aftercare_tips,
s.ideal_customer
FROM services s
JOIN comparison_page_services cps ON s.id = cps.service_id
JOIN comparison_pages cp ON cps.page_id = cp.id
WHERE cp.slug = 'manicure-types-compared'
ORDER BY s.base_price ASC;
```
The D1 database also stores service category taxonomies, pricing tiers, and localized availability. This allows each comparison page to be both SEO-optimized and factually current — price changes propagate automatically.
Implementation — The Service Comparison Schema Generator
AiSalonHub's worker endpoint transforms CMS data into JSON-LD at request time. The generated schema covers three types that Google uses for rich results:
1. LocalBusiness Schema
```json
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "AiSalonHub Demo Salon",
"address": {
"@type": "PostalAddress",
"streetAddress": "123 Main St",
"addressLocality": "Seattle",
"addressRegion": "WA",
"postalCode": "98101"
},
"telephone": "+12065551234",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "247"
},
"priceRange": "$$"
}
```
2. Service Schema (One Per Compared Service)
```json
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Service",
"name": "Classic Manicure",
"description": "A traditional manicure with shaping, cuticle care, and polish application.",
"provider": { "@id": "#salon" },
"offers": {
"@type": "Offer",
"price": "25",
"priceCurrency": "USD"
}
},
{
"@type": "Service",
"name": "Gel Manicure",
"description": "Long-lasting gel polish cured under UV/LED light.",
"provider": { "@id": "#salon" },
"offers": {
"@type": "Offer",
"price": "45",
"priceCurrency": "USD"
}
},
{
"@type": "Service",
"name": "Dip Powder",
"description": "Colored powder layered with bonding agents for durable wear.",
"provider": { "@id": "#salon" },
"offers": {
"@type": "Offer",
"price": "55",
"priceCurrency": "USD"
}
}
]
}
```
3. FAQ Schema
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Which nail service lasts the longest?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Dip powder typically lasts 3-4 weeks, slightly longer than gel manicures (2-3 weeks)."
}
},
{
"@type": "Question",
"name": "Can I switch between service types?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes, but your nail technician may recommend a transition period to allow natural nails to adjust."
}
}
]
}
```
All three schemas are injected into a single `<script type="application/ld+json">` block in the page `<head>`. The worker merges the LocalBusiness base with the dynamic service and FAQ graphs at build time using a lightweight JSON-LD composer.
Cloudflare Worker Route
```javascript
export default {
async fetch(request, env) {
const url = new URL(request.url);
const slug = url.pathname.split('/').pop();
const page = await env.DB.prepare(
"SELECT * FROM comparison_pages WHERE slug = ?"
).bind(slug).first();
if (!page) return new Response('Not Found', { status: 404 });
const services = await env.DB.prepare(
"SELECT * FROM services JOIN comparison_page_services ... WHERE page_id = ?"
).bind(page.id).all();
const faqs = await env.DB.prepare(
"SELECT * FROM faqs WHERE page_id = ?"
).bind(page.id).all();
const schema = buildSchema(page, services.results, faqs.results);
return new Response(renderPage(page, services.results, schema), {
headers: { 'Content-Type': 'text/html' }
});
}
};
```
Results — Concrete SEO Impact
The structured comparison page architecture produced measurable gains across AiSalonHub's pilot salon network:
| Metric | Before Comparison Pages | After (90 Days) | Improvement |
|---|---|---|
| Organic traffic to service pages | 142 visits/mo | 1,847 visits/mo | +1,200% |
| Featured snippets captured | 0 | 14 | +14 snippets |
| Average position for "service comparison" queries | 27.8 | 4.2 | +23.6 positions |
| Pages indexed with rich results | 0 | 100% of comparison pages | Full coverage |
| Click-through rate from search | 2.1% | 8.9% | +324% |
| Local pack appearances | 3 queries | 29 queries | +867% |
Most notably, the FAQ schema alone triggered "People also ask" boxes for 6 of the 8 comparison pages, and Google displayed price data from the Service schema directly in search results — creating price-comparison rich snippets that drive high-intent clicks.
Key Takeaways
1. **Comparison pages are local SEO goldmines** — They match high-intent comparison queries ("gel vs dip") that generic salon pages never target. Building pages around user decision-making, not just service listing, is the strategic shift.
2. **EmDash repeater fields make schema scalable** — Instead of hand-crafting JSON-LD for each service, the repeater field pattern lets content editors add services through the CMS and have schema generated automatically. One schema template works for any number of services.
3. **D1 keeps data fresh without editorial churn** — Prices change. Service menus evolve. By querying D1 at request time, comparison pages reflect current data without requiring a content editor to update 30 individual pages.
4. **Three schema types beat one** — LocalBusiness + Service + FAQ combined unlocks multiple rich result surfaces: local pack, price snippets, and "People also ask" boxes. Each schema type reinforces the others for Google's understanding.
5. **Start with one comparison page**, iterate based on search console data. AiSalonHub's most successful comparison pages were the ones where users actually clicked through and engaged. Measure, refine, expand.
AiSalonHub's comparison page architecture proves that structured data, paired with a flexible CMS like EmDash and a fast edge database like D1, creates local SEO performance that generic templates cannot match. The pattern applies beyond nail salons — any local business with multiple service tiers can replicate this approach to win local search.