local-business-seo
Skill1 starsChanged 49 days agoMITView on GitHub
Local SEO for company sites — LocalBusiness and Service JSON-LD, geo meta tags, areaServed hierarchy, regional keyword patterns, and the substance-over-keywords ranking approach. Use when adding local schema, regional landing pages, or optimizing for city-level search.
In this repository81 other files it gives its agents.
- accessibility-action-planSkill
- accessibility-auditSkill
- accessibility-finding-explainerSkill
- accessibility-report-summarySkill
- agent-config-authoringSkill
- agents-sdkSkill
- ai-ux-patternsSkill
- alt-text-generatorSkill
- anti-ai-copySkill
- aria-usage-reviewSkill
- astro-architectureSkill
- astro-client-scriptsSkill
- astro-component-a11y-reviewSkill
- before-after-accessibility-commsSkill
- bfsg-risk-language-softenerSkill
- biomeSkill
- blog-from-transcriptSkill
- brand-voice-dnaSkill
- case-study-from-auditSkill
- client-email-after-auditSkill
- cloudflare-email-serviceSkill
- cloudflare-one-migrationsSkill
- cloudflare-oneSkill
- cloudflareSkill
- complexity-judgmentSkill
- component-doc-writerSkill
- content-clarity-a11y-checkSkill
- content-qaSkill
- conversation-retrospectiveSkill
- darkmodeSkill
- deep-researchSkill
- design-directionsSkill
- design-systemsSkill
- durable-objectsSkill
- faq-from-issuesSkill
- form-accessibility-reviewSkill
- frontend-designSkill
- handoffSkill
- i18nSkill
- image-to-webpSkill
- keyboard-navigation-reviewSkill
- knowledge-baseSkill
- landing-and-site-builderSkill
- landingpage-from-briefingSkill
- lighthouse-axe-result-explainerSkill
- linkedin-post-from-contentSkill
- llm-councilSkill
- mcp-builderSkill
- mdx-contentSkill
- motion-designSkill
- native-html-uiSkill
- newsletter-from-auditSkill
- overlay-tool-risk-explainerSkill
- playwrightSkill
- post-auditSkill
- remotionSkill
- sandbox-migrate-to-nextSkill
- sandbox-nextSkill
- sandbox-sdkSkill
- sandbox-stableSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 175
Put the location in the title, preferably near the front.
- Distinctive: line 214
Search Console coverage after deploy.
- Very specific: line 215
Verify <meta name="geo.*"> in the HTML source.
- Distinctive: line 253
Test: remove the location word.
- Distinctive: line 330
Use the locale-matching form of city/region names on each language variant.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Local business SEO
Patterns for optimizing business websites for local search visibility (e.g. "web development <city>", "SEO agency <city>").
1. LocalBusiness JSON-LD (homepage)
---
// src/components/seo/LocalBusinessSchema.astro
export interface Props {
name: string;
description: string;
url: string;
telephone: string;
email?: string;
foundingDate?: string;
founder?: { name: string; jobTitle?: string };
address: {
street: string;
city: string;
postalCode: string;
region: string; // full region name, e.g. state/province
regionCode: string; // ISO 3166-2, e.g. "DE-XX"
country: string; // ISO, e.g. "DE"
};
geo: { latitude: number; longitude: number };
logo?: string;
image?: string;
sameAs?: string[];
openingHours?: string; // e.g. "Mo-Fr 09:00-17:00"
priceRange?: string; // e.g. "€€"
}
const props = Astro.props;
const schema = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'@id': `${props.url}#organization`,
name: props.name,
description: props.description,
url: props.url,
telephone: props.telephone,
...(props.email && { email: props.email }),
...(props.foundingDate && { foundingDate: props.foundingDate }),
...(props.founder && {
founder: {
'@type': 'Person',
name: props.founder.name,
...(props.founder.jobTitle && { jobTitle: props.founder.jobTitle }),
},
}),
address: {
'@type': 'PostalAddress',
streetAddress: props.address.street,
addressLocality: props.address.city,
postalCode: props.address.postalCode,
addressRegion: props.address.region,
addressCountry: props.address.country,
},
geo: {
'@type': 'GeoCoordinates',
latitude: props.geo.latitude,
longitude: props.geo.longitude,
},
...(props.logo && { logo: props.logo }),
...(props.image && { image: props.image }),
...(props.sameAs && { sameAs: props.sameAs }),
...(props.priceRange && { priceRange: props.priceRange }),
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />2. Geo meta tags (global in BaseHead / BaseLayout)
Emit on every page:
---
export interface GeoMeta {
region: string; // ISO 3166-2, e.g. "DE-XX"
placename: string;
latitude: number;
longitude: number;
}
const geo: GeoMeta = {
region: 'DE-XX',
placename: 'City',
latitude: 0,
longitude: 0,
};
---
<meta name="geo.region" content={geo.region} />
<meta name="geo.placename" content={geo.placename} />
<meta name="geo.position" content={`${geo.latitude};${geo.longitude}`} />
<meta name="ICBM" content={`${geo.latitude}, ${geo.longitude}`} />Store geo config centrally, e.g. src/config/business.ts:
export const businessConfig = {
name: 'Company name',
city: 'City',
region: 'Region',
regionCode: 'DE-XX',
country: 'DE',
geo: { latitude: 0, longitude: 0 },
telephone: '+49-…',
email: 'info@example.com',
url: 'https://example.com',
} as const;3. areaServed hierarchy (service pages)
For each service (web dev, SEO, e-commerce, etc.), emit a Service schema with a multi-level areaServed:
---
export interface Props {
serviceName: string;
serviceDescription: string;
serviceUrl: string;
providerName: string;
providerUrl: string;
}
const { serviceName, serviceDescription, serviceUrl, providerName, providerUrl } = Astro.props;
const schema = {
'@context': 'https://schema.org',
'@type': 'Service',
name: serviceName,
description: serviceDescription,
url: serviceUrl,
provider: {
'@type': 'LocalBusiness',
name: providerName,
url: providerUrl,
'@id': `${providerUrl}#organization`,
},
areaServed: [
{
'@type': 'City',
name: 'City',
containedInPlace: { '@type': 'State', name: 'Region' },
},
{
'@type': 'State',
name: 'Region',
containedInPlace: { '@type': 'Country', name: 'Country' },
},
{ '@type': 'Country', name: 'Country' },
],
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />4. Regional keywords in titles and descriptions
Rules:
- Put the location in the title, preferably near the front. Pattern:
Service City | Brand. - Work the location into the description naturally — no keyword stuffing.
- Title ≤ 60 chars, description ≤ 160 chars.
- Brand name at the end after
|. - H1 should contain the location. Pattern:
Service in CityorService from City.
Centralize per-page SEO strings:
// src/config/seo.ts
export const localSeoPages = {
home: {
title: 'Service A & Service B City | Brand',
description: 'Service A, Service B, and digital systems from City. Modern websites with Astro and Tailwind.',
},
serviceA: {
title: 'Service A City | Modern Solutions | Brand',
description: 'Service A from City — concrete outcomes with a modern stack.',
},
// ...
} as const;5. Implementation checklist
New local site
src/config/business.tswith company data, geo, contact.src/config/seo.tswith per-page localized titles/descriptions.- Geo meta tags rendered globally in the layout head.
LocalBusinessSchemaon the homepage.ServiceSchemaon every service page withareaServedhierarchy.- Titles/descriptions from
seo.tswired into<PageSEO>. - OG images with regional context.
Validation
- Google Rich Results Test for JSON-LD.
- Schema.org Validator.
- Search Console coverage after deploy.
- Verify
<meta name="geo.*">in the HTML source.
E2E pattern
test('homepage has LocalBusiness JSON-LD', async ({ page }) => {
await page.goto('/');
const jsonLd = await page.locator('script[type="application/ld+json"]').allTextContents();
const schemas = jsonLd.map((s) => JSON.parse(s));
const local = schemas.find((s) => s['@type'] === 'LocalBusiness');
expect(local).toBeDefined();
expect(local.address.addressLocality).toBeTruthy();
});
test('every page has geo meta tags', async ({ page }) => {
await page.goto('/');
await expect(page.locator('meta[name="geo.region"]')).toHaveAttribute('content', /.+/);
await expect(page.locator('meta[name="geo.placename"]')).toHaveAttribute('content', /.+/);
});
test('service pages have areaServed', async ({ page }) => {
await page.goto('/service-a');
const jsonLd = await page.locator('script[type="application/ld+json"]').allTextContents();
const service = jsonLd.map(JSON.parse).find((s) => s['@type'] === 'Service');
expect(service?.areaServed?.length).toBeGreaterThanOrEqual(3);
});6. What actually ranks — three principles
Pages that rank for many local keywords don't do it with black-hat tricks. They do three things consistently.
6.1 Substance, not keyword density
The location shows up 20–30 times on a strong service page — but never as repetition. Each occurrence is anchored in a real statement:
"Service from City for businesses in the region."
Test: remove the location word. Does the sentence still make sense? If yes, it's substance, not stuffing.
6.2 Process depth as a trust signal
The strongest pages explain not just what you do but how — concrete steps, phases, decision points. This demonstrates competence and keeps users on the page (lower bounce, longer dwell).
- 5-step processes with sub-headings.
- Phase 1 / Phase 2 / Phase 3 structure with real decision points.
- Specific over generic: "We check the CMS for X, Y, Z" beats "We analyze your site".
Depth target: ~4,000 words per service page; the strongest pages reach 6,000–8,000.
6.3 One landing page per service
Each offering gets its own URL, title tag, and meta description. More entry points, stronger internal linking.
/service-a — own H1, title, description
/service-b — own H1, title, description
/service-c — own H1, title, descriptionNot: a single /services page with sections for every offering.
6.4 Further ranking levers
- LocalBusiness markup (address + contact present).
- BreadcrumbList schema on all subpages.
- FAQ schema on service pages with real recurring questions.
- Flat heading hierarchy: one H1 → 2–4 H2 → H3 detail levels.
- Internal linking — each service page links to related services and the homepage.
BreadcrumbList schema
---
export interface Props { items: { name: string; url: string }[]; }
const { items } = Astro.props;
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: item.url,
})),
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />FAQ schema
---
export interface Props { questions: { question: string; answer: string }[]; }
const { questions } = Astro.props;
const schema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: questions.map((q) => ({
'@type': 'Question',
name: q.question,
acceptedAnswer: { '@type': 'Answer', text: q.answer },
})),
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />7. i18n notes
- Use the locale-matching form of city/region names on each language variant. City names often stay identical across languages; administrative region names typically get translated.
- JSON-LD
areaServed: use the language of the document. - Geo meta tags: language-independent (ISO codes), identical across all language versions.
hreflangcovered by sitemap config.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

