Skip to content

@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.


  • 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 @graph collections.
  • Social Meta Tag Composition: Generates complete OpenGraph (og:*) and Twitter Card (twitter:*) tag arrays with automatic HTML entity escaping.
  • Unified SEO Payload Pipeline: createSeoPayload aggregates 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/governance architectural rules preventing hand-rolled raw JSON-LD scripts.

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:

swarm.config.ts
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,
},
},
},
});

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/seo is registered in targets within swarm.config.ts.

Within the SiteSwarm monorepo workspace:

Terminal window
pnpm --filter <app-name> add @siteswarm/seo

Or declare workspace dependency in package.json:

{
"dependencies": {
"@siteswarm/seo": "workspace:*"
}
}

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 tag
const 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 Repair
const 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 / Agency
const 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" },
],
});

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);

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);

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

Below is the standard integration pattern used across SiteSwarm Astro applications:

apps/bakery/src/layouts/BaseLayout.astro
---
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>

Validate runtime user input or CMS payloads using the exported Zod validators:

import {
LocalBusinessSchemaValidator,
PersonSchemaValidator,
PostalAddressSchema,
GeoCoordinatesSchema,
OpeningHoursSpecificationSchema,
} from "@siteswarm/seo";
// 1. Validate LocalBusiness input
const 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 input
const 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());
}

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.
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.
  • 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.

Run tests and type checking in the package:

Terminal window
# Typecheck
pnpm --filter @siteswarm/seo check
# Unit tests
pnpm --filter @siteswarm/seo test
# Full workspace verification
pnpm run check