Every salon on AiSalonHub gets its own complete SEO footprint — JSON-LD schema, dynamic sitemap entries, server-rendered landing pages, and blog content — generated automatically at the edge with zero manual effort from the salon owner. This is the story of how we built that pipeline.
The Problem
Local SEO is the lifeblood of salon businesses. When someone searches "nail salon near me" or "gel manicure Brooklyn," the salons that appear on page one get the walk-ins. The ones that don't, don't. For a single-location salon, this is already challenging — it requires consistent NAP (Name, Address, Phone) citations, Google Business Profile optimization, localized content, backlinks, and structured data markup.
Now multiply that by hundreds of salons. AiSalonHub is a multi-tenant SaaS platform. Every salon on the platform needs its own local SEO presence. If we left each salon owner to handle this independently, the results would be patchy at best. Most nail salon owners are focused on running their business — not on technical SEO.
The core challenge was: how do we generate production-grade SEO for every tenant automatically, at scale, without human intervention, and without blowing up our Cloudflare Workers CPU limits?
The Solution
We built an automated SEO pipeline with three stages that feed into each other:
1. **Schema generation** — per-salon JSON-LD structured data injected into every page
2. **Dynamic sitemap generation** — per-salon sitemap.xml entries built on-the-fly at the edge
3. **Content distribution** — automated blog posts and landing pages for service keywords
Each stage runs on Cloudflare Workers, uses D1 for tenant data, and leverages edge caching so the SEO payloads are served from 300+ locations worldwide.
Architecture Overview
The pipeline operates as a chain of Cloudflare Workers connected by D1 and KV:
**Pipeline Components**
| Component | Runtime | Data Source | Cache Strategy |
| :--- | :--- | :--- | :--- |
| Schema Injector | Cloudflare Workers (HTMLRewriter) | D1 per-salon config | Edge TTL 1 hour |
| Sitemap Generator | Cloudflare Workers | D1 salon index | Edge TTL 30 min |
| Blog Renderer | Cloudflare Workers + Workers AI | D1 + AI-generated content | Edge TTL 1 day |
| Dynamic Router | Cloudflare Workers | KV route map | Edge TTL 5 min |
When a request arrives for `{salon-slug}.aisalonhub.com`, the Dynamic Router first checks KV for the salon tenant. If found, it passes the request through the Schema Injector, which uses Cloudflare's `HTMLRewriter` to insert JSON-LD into the `<head>` before the response hits the client. The Sitemap Generator runs on a CRON trigger, rebuilding sitemap index entries in D1 every 30 minutes.
Implementation
JSON-LD Schema Generation
Every salon page gets three types of structured data injected server-side:
```json
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Lily's Nails & Spa",
"image": "https://cdn.aisalonhub.com/tenants/lilys-nails/logo.webp",
"url": "https://lilys-nails.aisalonhub.com",
"telephone": "+1-555-0123",
"address": {
"@type": "PostalAddress",
"streetAddress": "1428 Elm Street",
"addressLocality": "Brooklyn",
"addressRegion": "NY",
"postalCode": "11201",
"addressCountry": "US"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"ratingCount": "203"
},
"priceRange": "$$",
"openingHours": "Mo-Fr 09:00-19:00, Sa 09:00-17:00"
}
```
We also inject `Service` schema for each salon's service menu and `Review` schema for recent customer reviews. The schema is generated by a dedicated Worker function `buildSchema(tenant)` that reads from D1 and outputs static JSON-LD strings — no runtime templates, no string interpolation overhead.
The injection happens via `HTMLRewriter`:
```javascript
class SchemaInjector {
constructor(schemaJSON) {
this.schema = schemaJSON;
}
element(element) {
const script = `<script type="application/ld+json">${this.schema}</script>`;
element.prepend(script, { html: true });
}
}
async function handleRequest(request, tenant) {
const schema = buildSchema(tenant);
const response = await fetch(request);
return new HTMLRewriter()
.on('head', new SchemaInjector(schema))
.transform(response);
}
```
Dynamic Sitemap Generation
Google needs to discover all salon pages — and with a multi-tenant setup, that means dynamically generating sitemaps per domain. Our sitemap Worker listens on `/sitemap.xml` and `/sitemaps/*.xml` for every tenant subdomain.
The pipeline:
1. Extract the subdomain from the request hostname
2. Query D1 for the tenant's published pages (booking, services, gallery, blog, about)
3. Build a sitemap XML response with proper `<lastmod>`, `<changefreq>`, and `<priority>` values
4. Set `Cache-Control: public, max-age=1800` and a `CF-Cache-Status` header for debugging
```xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://lilys-nails.aisalonhub.com/</loc>
<lastmod>2026-05-10</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://lilys-nails.aisalonhub.com/services</loc>
<lastmod>2026-05-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>https://lilys-nails.aisalonhub.com/booking</loc>
<lastmod>2026-05-10</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
```
A CRON trigger runs every 30 minutes to update a sitemap index that aggregates all tenants, which we submit to Google Search Console via the Indexing API.
Automated Blog Content
For service-based keywords (e.g., "gel manicure Brooklyn", "nail art Williamsburg"), we generate SEO-optimized blog posts using Workers AI (Llama 3.1). Each post targets a specific geo-keyword pair, includes the salon's NAP, embeds internal links to the booking page, and gets a unique sitemap entry.
Content is generated once and cached at the edge with a 24-hour TTL. The prompt template enforces structured output:
```
Generate a 300-word blog post about {service} in {neighborhood}.
Include the salon name: {salon_name}.
Mention the address: {address}.
Add a call-to-action to book online.
Use markdown headings.
```
The generated posts are stored in D1 with columns: `id, tenant_slug, keyword, service, neighborhood, content, created_at, published_at`. A separate Worker exposes these at `/blog/{keyword-slug}` with server-side rendering.
Dynamic Routing and Edge Caching
All of this is held together by a routing layer in our Cloudflare Workers entrypoint. The router:
1. Checks KV for `route:{hostname}` — if missing, queries D1 and populates KV with a 5-minute TTL
2. Bypasses the edge cache for authenticated requests (staff CMS)
3. Sets `cache-control` headers based on page type: public pages get 1-hour edge TTL, sitemaps get 30-minute TTL
4. Falls back to the origin SPA for non-SEO pages (admin dashboard, reporting)
Results
After deploying the automated SEO pipeline across all AiSalonHub tenants, we observed measurable improvements:
**SEO Performance Metrics**
| Metric | Before Pipeline | After Pipeline (3 months) |
| :--- | :--- | :--- |
| Pages indexed per salon (avg) | ~3 | ~14 |
| Organic search impressions (monthly) | 2,400 | 18,700 |
| Average Keyword Ranking (top 30) | 12 positions | 5 positions |
| Click-through rate | 1.8% | 4.2% |
| Salon-reported walk-ins from search | ~2/week | ~8/week |
The most significant impact came from the combination of JSON-LD schema (which powers rich snippets in search results) and the expanded sitemap coverage (which ensured Google discovered every service page). Salons with active blog content saw an additional 40% lift in impressions over those relying solely on static pages.
Key Takeaways
1. **Multi-tenant SEO must be automated.** You cannot rely on individual business owners to manage technical SEO. The platform must handle it at the infrastructure level.
2. **Edge computing is ideal for SEO generation.** Cloudflare Workers let us inject schema, generate sitemaps, and render SEO pages at the edge — close to users and search crawlers — with virtually no cold-start penalty.
3. **Structured data is the highest-ROI SEO investment.** JSON-LD schema directly unlocks rich results in Google: star ratings, price ranges, hours, and location cards. For local businesses, this is the difference between being a blue link and being a featured result.
4. **Sitemap automation closes the discovery loop.** Dynamic sitemaps ensure that every new service page, blog post, and gallery gets picked up by crawlers within hours, not weeks.
5. **Cache everything, but tier your TTLs.** Schema (1 hour), sitemaps (30 min), blog content (24 hours). The right cache strategy keeps D1 reads low and response times under 50ms at P95.
The pipeline runs on about 15% of our Workers CPU quota per tenant — a small price for a 7x increase in organic impressions. For any multi-tenant SaaS serving local businesses, this kind of automated SEO layer isn't optional. It's the moat.