@siteswarm/seo
@siteswarm/seo
Section titled “@siteswarm/seo”Headless dynamic Schema.org (JSON-LD), OpenGraph, and Twitter Card generator for SiteSwarm applications.
@siteswarm/seo provides edge-native structured data generation, OpenGraph metadata composition, and social preview formatting for the SiteSwarm fleet. It runs anywhere standard JavaScript executes—including Cloudflare Workers, Astro SSR, Node.js, and static edge prerender passes—with zero DOM or Node runtime dependencies.
🎯 Key Features
Section titled “🎯 Key Features”- Standardized Schema.org JSON-LD: Strongly typed builders for local commerce, automotive services, culinary venues, professional consultancies, and personal portfolios.
- Rich Business Metadata: Structured modeling of physical postal addresses, geographic coordinates (
lat/lng), opening hours specifications, price tiers, menus, and areas served. - Breadcrumb Graph Composition: Automatically formats nested breadcrumb items into canonical Schema.org
@graphcollections. - Social Meta Tag Composition: Generates complete OpenGraph (
og:*) and Twitter Card (twitter:*) tag arrays with automatic HTML entity escaping. - Unified SEO Payload Pipeline:
createSeoPayloadaggregates structured JSON-LD and meta tags in a single call. - Strict Zod Runtime Validation: Exported Zod schemas validate user-provided data against Schema.org constraints prior to publishing.
- Compile-Time Governance Compliance: Satisfies
@siteswarm/governancearchitectural rules preventing hand-rolled raw JSON-LD scripts.
🏛 Supported Schema.org Types
Section titled “🏛 Supported Schema.org Types”| Entity Type | Description | Primary Target Industries |
|---|---|---|
LocalBusiness |
Base Schema.org local business definition | General local businesses & storefronts |
Bakery |
Specialized local business for bakeries & cafes | Food & beverage, artisan bakers, patisseries |
AutoRepair |
Specialized local business for automotive mechanics | Auto repair shops, detailers, tire services |
Restaurant |
Food establishments with menus & cuisines | Dining establishments, bistros, eateries |
ProfessionalService |
Consultancies, legal, design, and software firms | Software agencies, architectural consultancies |
Person |
Individual profile and portfolio structured data | Engineers, designers, founders, consultants |
📦 Monorepo Governance & Manifest Mapping
Section titled “📦 Monorepo Governance & Manifest Mapping”SiteSwarm applications declare capability consumption in their swarm.config.ts manifest. Under the hood, the governance engine maps the dynamic-seo capability key to @siteswarm/seo:
import { defineConfig } from "@siteswarm/governance";
export default defineConfig({ name: "Green Leaf Bakery", slug: "bakery", deployment: { productionDomain: "greenleafbakery.com", previewDomainPattern: "bakery-*.siteswarm.dev", }, capabilities: { "dynamic-seo": { targets: ["src/layouts/BaseLayout.astro"], options: { schemaType: "Bakery", enableBreadcrumbs: true, generateOpenGraphImages: true, }, }, },});Architectural Linter Enforcement
Section titled “Architectural Linter Enforcement”SiteSwarm enforces architectural integrity through @siteswarm/governance:
arch/no-raw-schema-jsonld: Forbids hand-rolling raw<script type="application/ld+json">tags or untyped schema objects in client components. All JSON-LD must be generated through@siteswarm/seo.- Capability Target Verification: Asserts that every file importing
@siteswarm/seois registered intargetswithinswarm.config.ts.
🚀 Installation
Section titled “🚀 Installation”Within the SiteSwarm monorepo workspace:
pnpm --filter <app-name> add @siteswarm/seoOr declare workspace dependency in package.json:
{ "dependencies": { "@siteswarm/seo": "workspace:*" }}📖 Usage Examples
Section titled “📖 Usage Examples”1. Local Business / Storefront Schema (Bakery)
Section titled “1. Local Business / Storefront Schema (Bakery)”Construct Schema.org JSON-LD with geo-coordinates, operating hours, postal address, and cuisine metadata:
import { buildLocalBusinessSchema, renderJsonLdScript } from "@siteswarm/seo";
const bakerySchema = buildLocalBusinessSchema({ type: "Bakery", name: "Green Leaf Bakery", url: "https://greenleafbakery.com", image: "https://greenleafbakery.com/assets/logo.svg", telephone: "+1-503-555-0198", priceRange: "$$", address: { streetAddress: "412 Artisan Hearth Way", addressLocality: "Portland", addressRegion: "OR", postalCode: "97201", addressCountry: "US", }, geo: { latitude: 45.5152, longitude: -122.6784, }, openingHoursSpecification: [ { dayOfWeek: ["Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], opens: "06:30", closes: "15:00", }, ], servesCuisine: ["Artisan Sourdough", "French Pastries"], hasMenu: "https://greenleafbakery.com/menu",});
// Render directly into a <script type="application/ld+json"> HTML tagconst scriptHtml = renderJsonLdScript({ type: "Bakery", name: "Green Leaf Bakery", url: "https://greenleafbakery.com", /* ...options */});2. Automotive & Professional Service Schemas
Section titled “2. Automotive & Professional Service Schemas”import { buildLocalBusinessSchema } from "@siteswarm/seo";
// Automotive Repairconst autoRepairSchema = buildLocalBusinessSchema({ type: "AutoRepair", name: "Apex Auto Service", url: "https://apexautoservice.com", description: "Master Automotive Engineering, Diagnostics, and Repair", telephone: "+1-555-789-2040", address: { streetAddress: "1040 Performance Way", addressLocality: "Detroit", addressRegion: "MI", postalCode: "48201", },});
// Professional Consultancy / Agencyconst agencySchema = buildLocalBusinessSchema({ type: "ProfessionalService", name: "Apex Labs", url: "https://apexlabs.dev", description: "High-throughput distributed systems and edge architecture.", areaServed: "Global", priceRange: "$$$$",});3. Breadcrumbs with Automatic @graph Nesting
Section titled “3. Breadcrumbs with Automatic @graph Nesting”When breadcrumbs are supplied to buildLocalBusinessSchema, the output automatically transforms into a Schema.org @graph root containing both the primary business entity and a linked BreadcrumbList:
import { buildLocalBusinessSchema } from "@siteswarm/seo";
const breadcrumbSchema = buildLocalBusinessSchema({ type: "Restaurant", name: "Cafe Luna", url: "https://cafeluna.com", breadcrumbs: [ { name: "Home", item: "https://cafeluna.com" }, { name: "Menu", item: "https://cafeluna.com/menu" }, { name: "Dinner", item: "https://cafeluna.com/menu/dinner" }, ],});4. Person & ProfilePage Schema
Section titled “4. Person & ProfilePage Schema”Generates interconnected Person and ProfilePage nodes inside a Schema.org @graph:
import { buildPersonSchema, renderPersonJsonLdScript } from "@siteswarm/seo";
const personPayload = { name: "Jacob Miller", url: "https://jacobmiller22.com", jobTitle: "Forward Deployed & Autonomous Systems Engineer", alumniOf: { name: "Virginia Tech", url: "https://vt.edu", }, worksFor: { name: "SiteSwarm", url: "https://siteswarm.dev", }, sameAs: [ "https://github.com/jacobmiller22", "https://linkedin.com/in/jacobmiller22", ], knowsAbout: ["Distributed Systems", "TypeScript", "Cloudflare Workers"],};
const schema = buildPersonSchema(personPayload);const scriptTagHtml = renderPersonJsonLdScript(personPayload);5. OpenGraph & Twitter Card Meta Tags
Section titled “5. OpenGraph & Twitter Card Meta Tags”Generate strongly typed meta tags with HTML escaping:
import { buildMetaTags, renderMetaTagsHtml } from "@siteswarm/seo";
const metaInput = { title: "Green Leaf Bakery | Organic Sourdough & Pastries", description: "Artisan bakery located in Portland, OR specializing in naturally leavened breads.", canonicalUrl: "https://greenleafbakery.com", ogImage: "https://greenleafbakery.com/assets/og.png", ogType: "website" as const, siteName: "Green Leaf Bakery", twitterCard: "summary_large_image" as const,};
// Array of structured meta objects: { name?: string; property?: string; content: string }[]const metaTagObjects = buildMetaTags(metaInput);
// Pre-rendered HTML string for direct insertion into <head>const metaHtml = renderMetaTagsHtml(metaInput);6. Unified Payload (createSeoPayload)
Section titled “6. Unified Payload (createSeoPayload)”Combine structured JSON-LD and meta tags into a single execution step:
import { createSeoPayload } from "@siteswarm/seo";
const seo = createSeoPayload({ business: { type: "Bakery", name: "Sweet Hearth Bakery", url: "https://sweethearth.com", }, meta: { title: "Sweet Hearth Bakery", description: "Daily baked goods in Portland", canonicalUrl: "https://sweethearth.com", ogImage: "https://sweethearth.com/og.jpg", },});
// Access results:// seo.schema -> Raw Schema.org JSON-LD object (or null)// seo.jsonLdHtml -> Ready-to-inject <script type="application/ld+json"> tag// seo.metaTags -> MetaTagEntry[]// seo.metaHtml -> Ready-to-inject <meta> tag string🌐 Astro Integration (BaseLayout.astro)
Section titled “🌐 Astro Integration (BaseLayout.astro)”Below is the standard integration pattern used across SiteSwarm Astro applications:
---import { buildLocalBusinessSchema, buildMetaTags } from "@siteswarm/seo";import manifest from "../../../swarm.config.ts";
interface Props { title: string; description: string; ogImage?: string; canonicalUrl?: string;}
const { title, description, ogImage = "/assets/og.png", canonicalUrl = Astro.url.href,} = Astro.props;
const structuredData = buildLocalBusinessSchema({ type: (manifest.capabilities["dynamic-seo"]?.options?.schemaType as any) || "Bakery", name: manifest.name, url: `https://${manifest.deployment.productionDomain}`, image: `https://${manifest.deployment.productionDomain}${ogImage}`, telephone: "+1-503-555-0198", priceRange: "$$", address: { streetAddress: "412 Artisan Hearth Way", addressLocality: "Portland", addressRegion: "OR", postalCode: "97201", addressCountry: "US", }, geo: { latitude: 45.5152, longitude: -122.6784, }, openingHoursSpecification: [ { dayOfWeek: ["Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], opens: "06:30", closes: "15:00", }, ],});---
<!doctype html><html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{title}</title> <meta name="description" content={description} /> <link rel="canonical" href={canonicalUrl} />
<!-- OpenGraph & Twitter Cards --> <meta property="og:type" content="website" /> <meta property="og:url" content={canonicalUrl} /> <meta property="og:title" content={title} /> <meta property="og:description" content={description} /> <meta property="og:image" content={new URL(ogImage, Astro.url)} /> <meta name="twitter:card" content="summary_large_image" />
<!-- Structured Data JSON-LD (dynamic-seo capability) --> <script type="application/ld+json" set:html={JSON.stringify(structuredData)} /> </head> <body> <slot /> </body></html>🛡 Runtime Validation with Zod
Section titled “🛡 Runtime Validation with Zod”Validate runtime user input or CMS payloads using the exported Zod validators:
import { LocalBusinessSchemaValidator, PersonSchemaValidator, PostalAddressSchema, GeoCoordinatesSchema, OpeningHoursSpecificationSchema,} from "@siteswarm/seo";
// 1. Validate LocalBusiness inputconst businessResult = LocalBusinessSchemaValidator.safeParse({ type: "Bakery", name: "Artisan Breadworks", url: "https://artisanbreadworks.com", email: "contact@artisanbreadworks.com", address: { streetAddress: "100 Flour Way", addressLocality: "Seattle", addressRegion: "WA", postalCode: "98101", },});
if (!businessResult.success) { console.error("Invalid business schema:", businessResult.error.format());} else { console.log("Validated payload:", businessResult.data);}
// 2. Validate Person inputconst personResult = PersonSchemaValidator.safeParse({ name: "Alex Doe", url: "https://alexdoe.dev", sameAs: ["https://github.com/alexdoe"],});
if (!personResult.success) { console.error("Invalid person schema:", personResult.error.format());}📚 API Reference
Section titled “📚 API Reference”Schema Builders & Renderers
Section titled “Schema Builders & Renderers”| Export | Signature | Description |
|---|---|---|
buildLocalBusinessSchema |
(input: LocalBusinessSchemaInput) => Record<string, unknown> |
Constructs Schema.org LocalBusiness or subtype object (or @graph if breadcrumbs present). |
buildPersonSchema |
(input: PersonSchemaInput) => Record<string, unknown> |
Constructs @graph with ProfilePage and Person nodes. |
buildPostalAddress |
(address: PostalAddressInput) => object |
Generates standard PostalAddress Schema.org object. |
buildGeoCoordinates |
(geo: GeoCoordinatesInput) => object |
Generates standard GeoCoordinates Schema.org object. |
buildOpeningHours |
(specs: OpeningHoursSpecificationInput[]) => object[] |
Generates OpeningHoursSpecification objects. |
buildBreadcrumbs |
(items: BreadcrumbItem[]) => object |
Generates BreadcrumbList Schema.org object. |
renderJsonLdScript |
(input: LocalBusinessSchemaInput) => string |
Returns HTML <script type="application/ld+json"> tag. |
renderPersonJsonLdScript |
(input: PersonSchemaInput) => string |
Returns HTML <script type="application/ld+json"> tag for a person. |
createSeoPayload |
(options: { business?; person?; meta? }) => SeoPayload |
Unified generator for schema, meta tags, and HTML strings. |
Meta Tag Composition
Section titled “Meta Tag Composition”| Export | Signature | Description |
|---|---|---|
buildMetaTags |
(input: MetaTagsInput) => MetaTagEntry[] |
Generates array of OpenGraph and Twitter card meta objects. |
renderMetaTagsHtml |
(input: MetaTagsInput) => string |
Serializes meta tag objects into escaped <meta> tags. |
Zod Validators
Section titled “Zod Validators”LocalBusinessSchemaValidator: Validates local business properties and nested sub-schemas.PersonSchemaValidator: Validates person and profile schema inputs.PostalAddressSchema: Validates street, locality, region, postal code, and country.GeoCoordinatesSchema: Validates numeric latitude and longitude coordinates.OpeningHoursSpecificationSchema: Validates opening hours intervals (HH:MM) and days of the week.
🧪 Testing & Verification
Section titled “🧪 Testing & Verification”Run tests and type checking in the package:
# Typecheckpnpm --filter @siteswarm/seo check
# Unit testspnpm --filter @siteswarm/seo test
# Full workspace verificationpnpm run check