{"_id":"@bliztek/seo","_rev":"5-77bef15ea3c90d963c1f15b0b93cb84e","name":"@bliztek/seo","dist-tags":{"latest":"2.1.1","next":"2.1.1-rc.0"},"versions":{"1.0.0":{"name":"@bliztek/seo","version":"1.0.0","license":"MIT","_id":"@bliztek/seo@1.0.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/seo#readme","bugs":{"url":"https://github.com/bliztek/seo/issues"},"dist":{"shasum":"0b2e37a883bb6ef885a3f2301d1cbcf7ea44542b","tarball":"https://registry.npmjs.org/@bliztek/seo/-/seo-1.0.0.tgz","fileCount":12,"integrity":"sha512-/hL8OSwNMPtMGq5fnyvxxkGEEOUD+WMagtAtPxMVtHyj51tOa9kyHwNubORlbNbJY7W7UkMVqn8X9PBeldYWnQ==","signatures":[{"sig":"MEUCID7qxzoP7vC/lHyF+avKISwmmFD0JrlCuIXL5baAnBI9AiEA8+EY1PJ/o2QXgJbMf0BXfBOpXuqDIAqbD5cMsPuY21c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":29217},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./json-ld":{"types":"./dist/json-ld/index.d.ts","import":"./dist/json-ld/index.js","require":"./dist/json-ld/index.cjs"}},"gitHead":"255e3d2d115482a0ccb6bfafeb54b8649dd85667","scripts":{"test":"cd ../.. && vitest run packages/seo","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/seo.git","type":"git"},"_npmVersion":"11.9.0","description":"Zero-dependency SEO metadata generation and JSON-LD structured data components","directories":{},"sideEffects":false,"_nodeVersion":"25.6.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^15.0.0","tsup":"^8.0.0","react":"^19.0.0","react-dom":"^19.0.0","typescript":"^5.0.0","@types/react":"^19.0.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo_1.0.0_1773405174017_0.6241631517462767","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bliztek/seo","version":"1.0.1","license":"MIT","_id":"@bliztek/seo@1.0.1","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://github.com/bliztek/seo#readme","bugs":{"url":"https://github.com/bliztek/seo/issues"},"dist":{"shasum":"33480a228c4afbf90764b8e604b9a9e5020cee8c","tarball":"https://registry.npmjs.org/@bliztek/seo/-/seo-1.0.1.tgz","fileCount":13,"integrity":"sha512-7BTfH9fLxj0pFct0HXqdkxgzYOYxu2S9DahEfbc5tbzypztOYOAYeoJ1NTkdcUNMwi2nSnmvrs1Tze5y1cZcpw==","signatures":[{"sig":"MEUCIQCBdk7vFgksafQS3oDlCe8mj2B6GuFCVmiW39+mQuK2zAIgSvLnBpuu4dK/9FuS3F4U8PThVDCXSjwZLZF9qQGF4Vk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33882},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./json-ld":{"types":"./dist/json-ld/index.d.ts","import":"./dist/json-ld/index.js","require":"./dist/json-ld/index.cjs"}},"gitHead":"343c73c26422d6518be3fea19bb3ef57377a06b2","scripts":{"test":"cd ../.. && vitest run packages/seo","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://github.com/bliztek/seo.git","type":"git"},"_npmVersion":"11.9.0","description":"Zero-dependency SEO metadata generation and JSON-LD structured data components","directories":{},"sideEffects":false,"_nodeVersion":"25.6.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^15.0.0","tsup":"^8.0.0","react":"^19.0.0","react-dom":"^19.0.0","typescript":"^5.0.0","@types/react":"^19.0.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo_1.0.1_1773405420012_0.5925304960289921","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@bliztek/seo","version":"2.1.0","keywords":["seo","nextjs","next.js","metadata","json-ld","structured-data","schema.org","open-graph","twitter-cards","sitemap","robots","react","llms-txt","ai-seo","generative-engine-optimization","ai-crawlers"],"license":"MIT","_id":"@bliztek/seo@2.1.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://gitlab.com/bliztek/seo#readme","bugs":{"url":"https://gitlab.com/bliztek/seo/-/issues"},"dist":{"shasum":"b33d63d3b216572260df530f8373e4547a8b7aca","tarball":"https://registry.npmjs.org/@bliztek/seo/-/seo-2.1.0.tgz","fileCount":18,"integrity":"sha512-ObQWEOyd9odLNOVNGSiSG10MIUgq1YN3NEhw1lSv2CcMMx9nEV/XeqjIF44Ij8Nuv/wkgODoc+Q1LDCyS6tzng==","signatures":[{"sig":"MEQCIGa0K1PAWC90BhjT+OLDSmZtxMExa/2T8JP1Se8cl5Z9AiBBR2aYMpi3h5N9n/dipweFWP3gPbs+9ngODGZJ482syA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":193039},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./ai":{"types":"./dist/ai/index.d.ts","import":"./dist/ai/index.js","require":"./dist/ai/index.cjs"},"./json-ld":{"types":"./dist/json-ld/index.d.ts","import":"./dist/json-ld/index.js","require":"./dist/json-ld/index.cjs"}},"gitHead":"98ff0646705dab75282f6ef3c5016c39af5a94ed","scripts":{"test":"vitest run","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://gitlab.com/bliztek/seo.git","type":"git"},"_npmVersion":"11.12.1","description":"Zero-dependency SEO metadata, JSON-LD structured data, sitemap/robots helpers, validation, and LLM/AI search optimization for Next.js","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.3","tsup":"^8.5.1","react":"^19.2.5","vitest":"^4.1.3","react-dom":"^19.2.5","typescript":"^6.0.2","@types/react":"^19.2.14"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo_2.1.0_1775687725241_0.40551814414616905","host":"s3://npm-registry-packages-npm-production"}},"2.1.1-rc.0":{"name":"@bliztek/seo","version":"2.1.1-rc.0","keywords":["seo","nextjs","next.js","metadata","json-ld","structured-data","schema.org","open-graph","twitter-cards","sitemap","robots","react","llms-txt","ai-seo","generative-engine-optimization","ai-crawlers"],"license":"MIT","_id":"@bliztek/seo@2.1.1-rc.0","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"homepage":"https://gitlab.com/bliztek/seo#readme","bugs":{"url":"https://gitlab.com/bliztek/seo/-/issues"},"dist":{"shasum":"ce8d2162b91f6497bf7d9d6f8a1e6d9e55275908","tarball":"https://registry.npmjs.org/@bliztek/seo/-/seo-2.1.1-rc.0.tgz","fileCount":20,"integrity":"sha512-69rHpZQBLQckdSde/2WZ059S+WSXT1o+3M4j8eZr7xc+3qwpzCF6A9rbBgvzT4HgkmbwRIK4cNTDIa42Yf67Gg==","signatures":[{"sig":"MEQCIAeg7cAguBAC33qUkGrMWKaOeP5dfbC3WKG0H5+dx5u/AiBsB6PwMw81V+xB7uvT+ocGBk5hByFoxEw/g1A5Ph6ZSg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":205596},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./ai":{"types":"./dist/ai/index.d.ts","import":"./dist/ai/index.js","require":"./dist/ai/index.cjs"},"./json-ld":{"types":"./dist/json-ld/index.d.ts","import":"./dist/json-ld/index.js","require":"./dist/json-ld/index.cjs"}},"gitHead":"524c8cd5c8644f78cbe37b279da28edb6f76315c","scripts":{"test":"vitest run","build":"tsup","prepublishOnly":"npm run build"},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"repository":{"url":"git+https://gitlab.com/bliztek/seo.git","type":"git"},"_npmVersion":"11.12.1","description":"Zero-dependency SEO metadata, JSON-LD structured data, sitemap/robots helpers, validation, and LLM/AI search optimization for Next.js","directories":{},"sideEffects":false,"_nodeVersion":"25.9.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"next":"^16.2.3","tsup":"^8.5.1","react":"^19.2.5","vitest":"^4.1.3","react-dom":"^19.2.5","typescript":"^6.0.2","@types/react":"^19.2.14"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo_2.1.1-rc.0_1777649738248_0.3869353950237988","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"name":"@bliztek/seo","version":"2.1.1","description":"Zero-dependency SEO metadata, JSON-LD structured data, sitemap/robots helpers, validation, and LLM/AI search optimization for Next.js","sideEffects":false,"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./json-ld":{"types":"./dist/json-ld/index.d.ts","import":"./dist/json-ld/index.js","require":"./dist/json-ld/index.cjs"},"./ai":{"types":"./dist/ai/index.d.ts","import":"./dist/ai/index.js","require":"./dist/ai/index.cjs"}},"scripts":{"build":"tsup","prepublishOnly":"npm run build","test":"vitest run","publish:rc":"bash scripts/publish.sh --tag next","publish:latest":"bash scripts/publish.sh"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@types/react":"^19.2.14","next":"^16.2.3","react":"^19.2.5","react-dom":"^19.2.5","tsup":"^8.5.1","typescript":"^6.0.2","vitest":"^4.1.3"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"license":"MIT","keywords":["seo","nextjs","next.js","metadata","json-ld","structured-data","schema.org","open-graph","twitter-cards","sitemap","robots","react","llms-txt","ai-seo","generative-engine-optimization","ai-crawlers"],"repository":{"type":"git","url":"git+https://gitlab.com/bliztek/seo.git"},"homepage":"https://gitlab.com/bliztek/seo#readme","bugs":{"url":"https://gitlab.com/bliztek/seo/-/issues"},"engines":{"node":">=18"},"gitHead":"b3a3e533ed9d124e7603d3c992def8515965d994","_id":"@bliztek/seo@2.1.1","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-slFTwoCGxftxwvWr29MThh+t6DGJW3PGi6dm51kq4k7KP+TzkY0gavMkQOjjjF90vqQzy2wTnOPV5UZHk423Aw==","shasum":"3653568bee1b6c0ef35430311c9fb724fe9f004d","tarball":"https://registry.npmjs.org/@bliztek/seo/-/seo-2.1.1.tgz","fileCount":20,"unpackedSize":206127,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBlJ3vYWnSBsdieeLkatYPa71QVE/pXaa7wy0IeESFb6AiA4RkqSLP1xo8ZQR0t/2CTamJjZOPjc3nEzCXulbkEVCA=="}]},"_npmUser":{"name":"bliztek","email":"sbrown@bliztek.com"},"directories":{},"maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/seo_2.1.1_1777667516364_0.043411543631366234"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-13T12:32:53.944Z","modified":"2026-05-01T20:31:56.615Z","1.0.0":"2026-03-13T12:32:54.149Z","1.0.1":"2026-03-13T12:37:00.141Z","2.1.0":"2026-04-08T22:35:25.395Z","2.1.1-rc.0":"2026-05-01T15:35:38.451Z","2.1.1":"2026-05-01T20:31:56.508Z"},"bugs":{"url":"https://gitlab.com/bliztek/seo/-/issues"},"license":"MIT","homepage":"https://gitlab.com/bliztek/seo#readme","keywords":["seo","nextjs","next.js","metadata","json-ld","structured-data","schema.org","open-graph","twitter-cards","sitemap","robots","react","llms-txt","ai-seo","generative-engine-optimization","ai-crawlers"],"repository":{"type":"git","url":"git+https://gitlab.com/bliztek/seo.git"},"description":"Zero-dependency SEO metadata, JSON-LD structured data, sitemap/robots helpers, validation, and LLM/AI search optimization for Next.js","maintainers":[{"name":"bliztek","email":"sbrown@bliztek.com"}],"readme":"# @bliztek/seo\n\nZero-dependency SEO metadata, JSON-LD structured data, sitemap/robots helpers, validation, and LLM/AI search optimization for Next.js.\n\n## Installation\n\n```bash\npnpm add @bliztek/seo\n```\n\nReact is an optional peer dependency — only required if you use the JSON-LD components.\n\n## Entry Points\n\n| Import | What you get |\n|---|---|\n| `@bliztek/seo` | Metadata utilities, sitemap/robots helpers, validation, feed adapter, types |\n| `@bliztek/seo/json-ld` | React JSON-LD components (26 components) |\n| `@bliztek/seo/ai` | LLM/AI search optimization — llms.txt, AI crawlers, AI robots, `JsonLdGraph` |\n\n## Quick Start\n\n### Site Config\n\nCreate a central config object for your site:\n\n```ts\nimport type { SiteConfig } from \"@bliztek/seo\";\n\nexport const siteConfig: SiteConfig = {\n  url: \"https://example.com\",\n  titleSuffix: \"My Site\",\n  defaultImage: \"/assets/cover.png\",\n  defaultAuthor: \"Jane Doe\",\n  blogPathPrefix: \"/blog/post\",\n  siteName: \"My Site\",           // → og:site_name\n  twitterSite: \"@mysite\",        // → twitter:site\n  icons: {\n    icon: \"/favicon.ico\",\n  },\n};\n```\n\n### Generate Metadata\n\n```ts\nimport { generateSEO } from \"@bliztek/seo\";\nimport { siteConfig } from \"./seo-config\";\n\n// Basic page metadata\nexport const metadata = generateSEO(\n  {\n    title: \"About Us\",\n    description: \"Learn more about our team.\",\n    url: \"/about\",\n  },\n  siteConfig\n);\n\n// Article with full OG support\nexport const articleMetadata = generateSEO(\n  {\n    title: \"How We Built Our Platform\",\n    description: \"A deep dive into our architecture.\",\n    url: \"/blog/how-we-built\",\n    ogType: \"article\",\n    twitterCreator: \"@janedoe\",\n    imageAlt: \"Architecture diagram\",\n    article: {\n      publishedTime: \"2026-01-15T00:00:00Z\",\n      modifiedTime: \"2026-02-01T00:00:00Z\",\n      author: \"Jane Doe\",\n      section: \"Technology\",\n      tags: [\"architecture\", \"nextjs\"],\n    },\n    alternateLanguages: {\n      es: \"/es/blog/como-construimos\",\n      fr: \"/fr/blog/comment-nous-avons-construit\",\n    },\n  },\n  siteConfig\n);\n```\n\n### Blog Post Metadata\n\n`blogPostToSEOProps` automatically sets `ogType: \"article\"` and populates article OG tags from your post metadata:\n\n```ts\nimport { blogPostToSEOProps, generateSEO } from \"@bliztek/seo\";\nimport { siteConfig } from \"./seo-config\";\n\nconst seoProps = blogPostToSEOProps(slug, postMetadata, siteConfig);\nexport const metadata = generateSEO(seoProps, siteConfig);\n```\n\n### Composable SEO Objects\n\n```ts\nimport { createSEO } from \"@bliztek/seo\";\n\nconst baseSEO = {\n  website: \"https://example.com\",\n  url: \"https://example.com\",\n  title: \"My Site\",\n  description: \"Default description\",\n  image: \"/cover.png\",\n};\n\nconst aboutSEO = createSEO(baseSEO, \"/about\", {\n  title: \"About Us\",\n  description: \"Learn more about our team.\",\n});\n```\n\n> **Note:** `generateSEO` is the recommended API for most use cases. `createSEO` is a lightweight alternative for building composable SEO objects.\n\n### URL Handling\n\nAll URL-shaped props throughout the library (`logo`, `contactUrl`, `image`, `defaultImage`, `favicon`, `siteUrl`, `url`, `feedLinks.*`, etc.) accept either:\n\n- A path relative to the site URL (e.g. `/logo.png`) — joined to the configured site URL.\n- An already-absolute URL (e.g. `https://cdn.example.com/logo.png`) — passed through unchanged.\n- A protocol-relative URL (e.g. `//cdn.example.com/logo.png`) — passed through unchanged.\n\nTrailing slashes on the site URL and missing leading slashes on paths are normalized automatically.\n\nIf a URL-shaped prop is `undefined` or empty, the helper returns the bare base URL rather than throwing — so accidentally passing `undefined` to a non-optional prop (e.g. via `// @ts-ignore`) will produce a bare-origin URL like `https://example.com` in the rendered JSON-LD, not a runtime error. TypeScript types enforce non-optional props at compile time; this is a fallback contract for the rare cases TypeScript is bypassed.\n\n### OG Image URLs\n\n```ts\nimport { buildOgImageUrl } from \"@bliztek/seo\";\n\nconst ogUrl = buildOgImageUrl(\"My Page Title\", {\n  subtitle: \"Category\",\n  type: \"blog\",\n});\n// → \"/api/og?title=My+Page+Title&subtitle=Category&type=blog\"\n```\n\n## JSON-LD Components\n\nAll 26 components render a `<script type=\"application/ld+json\">` tag with XSS-safe serialization.\n\n### BreadcrumbJsonLd\n\n```tsx\nimport { BreadcrumbJsonLd } from \"@bliztek/seo/json-ld\";\n\n<BreadcrumbJsonLd\n  items={[\n    { name: \"Home\", url: \"https://example.com\" },\n    { name: \"Blog\", url: \"https://example.com/blog\" },\n  ]}\n/>;\n```\n\n### BlogPostingJsonLd\n\n```tsx\nimport { BlogPostingJsonLd } from \"@bliztek/seo/json-ld\";\n\n<BlogPostingJsonLd\n  title=\"My Post\"\n  description=\"Post description\"\n  date=\"2025-01-15\"\n  slug=\"my-post\"\n  siteUrl=\"https://example.com\"\n  blogPathPrefix=\"/blog/post\"\n  author={{\n    name: \"Jane Doe\",\n    url: \"https://janedoe.com\",\n    jobTitle: \"Engineer\",\n  }}\n  publisher={{\n    name: \"My Site\",\n    url: \"https://example.com\",\n    logoUrl: \"https://example.com/logo.png\",\n  }}\n/>;\n```\n\n### ArticleJsonLd\n\n```tsx\nimport { ArticleJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ArticleJsonLd\n  headline=\"How to Build a Blog\"\n  description=\"A comprehensive guide\"\n  datePublished=\"2026-01-15\"\n  dateModified=\"2026-02-01\"\n  url=\"https://example.com/articles/build-blog\"\n  image=\"https://example.com/article-img.jpg\"\n  articleSection=\"Technology\"\n  wordCount={2500}\n  author={{ name: \"Jane Doe\", url: \"https://janedoe.com\" }}\n  publisher={{\n    name: \"My Site\",\n    url: \"https://example.com\",\n    logoUrl: \"https://example.com/logo.png\",\n  }}\n/>;\n```\n\n### NewsArticleJsonLd\n\n```tsx\nimport { NewsArticleJsonLd } from \"@bliztek/seo/json-ld\";\n\n<NewsArticleJsonLd\n  headline=\"Breaking: New Discovery\"\n  description=\"Scientists announce breakthrough\"\n  datePublished=\"2026-01-15\"\n  url=\"https://news.com/discovery\"\n  author={[{ name: \"Alice\" }, { name: \"Bob\" }]}\n  publisher={{\n    name: \"News Corp\",\n    url: \"https://news.com\",\n    logoUrl: \"https://news.com/logo.png\",\n  }}\n  articleSection=\"Science\"\n  keywords={[\"science\", \"discovery\"]}\n/>;\n```\n\n### FAQPageJsonLd\n\n```tsx\nimport { FAQPageJsonLd } from \"@bliztek/seo/json-ld\";\n\n<FAQPageJsonLd\n  faqs={[\n    { question: \"What is this?\", answer: \"A great package.\" },\n  ]}\n/>;\n```\n\n### ProductJsonLd\n\n```tsx\nimport { ProductJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ProductJsonLd\n  name=\"Premium Widget\"\n  description=\"The best widget money can buy\"\n  image=\"https://example.com/widget.jpg\"\n  brand=\"WidgetCo\"\n  sku=\"WIDGET-001\"\n  offers={{\n    price: 29.99,\n    priceCurrency: \"USD\",\n    availability: \"InStock\",\n    url: \"https://example.com/buy\",\n  }}\n  aggregateRating={{ ratingValue: 4.5, reviewCount: 100 }}\n/>;\n```\n\n### EventJsonLd\n\n```tsx\nimport { EventJsonLd } from \"@bliztek/seo/json-ld\";\n\n<EventJsonLd\n  name=\"Tech Conference 2026\"\n  startDate=\"2026-06-01T09:00:00\"\n  endDate=\"2026-06-03T17:00:00\"\n  description=\"The premier tech event\"\n  location={{ name: \"Convention Center\", address: \"123 Main St, Springfield\" }}\n  offers={{ price: 199, priceCurrency: \"USD\", availability: \"InStock\" }}\n  organizer={{ name: \"TechEvents Inc\", url: \"https://techevents.com\" }}\n  eventStatus=\"EventScheduled\"\n  eventAttendanceMode=\"MixedEventAttendanceMode\"\n/>;\n```\n\n### VideoJsonLd\n\n```tsx\nimport { VideoJsonLd } from \"@bliztek/seo/json-ld\";\n\n<VideoJsonLd\n  name=\"Product Demo\"\n  description=\"See our product in action\"\n  thumbnailUrl=\"https://example.com/thumb.jpg\"\n  uploadDate=\"2026-01-15\"\n  duration=\"PT5M30S\"\n  contentUrl=\"https://example.com/video.mp4\"\n  embedUrl=\"https://example.com/embed/video\"\n/>;\n```\n\n### WebSiteJsonLd\n\nEnables the sitelinks search box in Google:\n\n```tsx\nimport { WebSiteJsonLd } from \"@bliztek/seo/json-ld\";\n\n<WebSiteJsonLd\n  name=\"My Site\"\n  url=\"https://example.com\"\n  searchAction={{\n    target: \"https://example.com/search?q={search_term_string}\",\n  }}\n/>;\n```\n\n### HowToJsonLd\n\n```tsx\nimport { HowToJsonLd } from \"@bliztek/seo/json-ld\";\n\n<HowToJsonLd\n  name=\"How to Make Coffee\"\n  description=\"A simple guide to brewing coffee\"\n  totalTime=\"PT10M\"\n  steps={[\n    { name: \"Boil water\", text: \"Heat water to 200°F\" },\n    { name: \"Add grounds\", text: \"Put coffee grounds in filter\" },\n    { name: \"Pour and wait\", text: \"Pour water over grounds, wait 4 minutes\" },\n  ]}\n  supply={[\"Coffee grounds\", \"Water\", \"Filter\"]}\n  tool={[\"Coffee maker\", \"Kettle\"]}\n/>;\n```\n\n### ReviewJsonLd\n\n```tsx\nimport { ReviewJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ReviewJsonLd\n  itemReviewed={{ type: \"Product\", name: \"Widget Pro\" }}\n  author={{ name: \"John Doe\" }}\n  reviewRating={{ ratingValue: 5, bestRating: 5 }}\n  reviewBody=\"Excellent product, highly recommended!\"\n  datePublished=\"2026-01-15\"\n/>;\n```\n\n### AggregateRatingJsonLd\n\n```tsx\nimport { AggregateRatingJsonLd } from \"@bliztek/seo/json-ld\";\n\n<AggregateRatingJsonLd\n  itemReviewed={{ type: \"Restaurant\", name: \"Burger Place\" }}\n  ratingValue={4.2}\n  reviewCount={250}\n  bestRating={5}\n/>;\n```\n\n### PersonJsonLd\n\n```tsx\nimport { PersonJsonLd } from \"@bliztek/seo/json-ld\";\n\n<PersonJsonLd\n  name=\"Jane Doe\"\n  url=\"https://janedoe.com\"\n  image=\"https://janedoe.com/photo.jpg\"\n  jobTitle=\"Software Engineer\"\n  worksFor={{ name: \"Tech Co\", url: \"https://techco.com\" }}\n  sameAs={[\"https://github.com/janedoe\", \"https://twitter.com/janedoe\"]}\n/>;\n```\n\n### ProfilePageJsonLd\n\n```tsx\nimport { ProfilePageJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ProfilePageJsonLd\n  name=\"Jane's Profile\"\n  url=\"https://example.com/team/jane\"\n  mainEntity={{\n    name: \"Jane Doe\",\n    jobTitle: \"Lead Engineer\",\n    sameAs: [\"https://github.com/janedoe\"],\n  }}\n/>;\n```\n\n### CourseJsonLd\n\n```tsx\nimport { CourseJsonLd } from \"@bliztek/seo/json-ld\";\n\n<CourseJsonLd\n  name=\"Introduction to Machine Learning\"\n  description=\"Learn the fundamentals of ML\"\n  provider={{ name: \"Tech University\", url: \"https://techuni.edu\" }}\n  courseCode=\"ML101\"\n  hasCourseInstance={{\n    courseMode: \"online\",\n    startDate: \"2026-09-01\",\n  }}\n/>;\n```\n\n### JobPostingJsonLd\n\n```tsx\nimport { JobPostingJsonLd } from \"@bliztek/seo/json-ld\";\n\n<JobPostingJsonLd\n  title=\"Senior Software Engineer\"\n  description=\"Build scalable web applications\"\n  datePosted=\"2026-01-15\"\n  validThrough=\"2026-03-15\"\n  hiringOrganization={{ name: \"Tech Co\", url: \"https://techco.com\" }}\n  employmentType=\"FULL_TIME\"\n  remote={true}\n  baseSalary={{\n    currency: \"USD\",\n    value: { minValue: 120000, maxValue: 180000 },\n    unitText: \"YEAR\",\n  }}\n/>;\n```\n\n### SoftwareAppJsonLd\n\n```tsx\nimport { SoftwareAppJsonLd } from \"@bliztek/seo/json-ld\";\n\n<SoftwareAppJsonLd\n  name=\"MyApp\"\n  operatingSystem=\"Windows, macOS, Linux\"\n  applicationCategory=\"DeveloperApplication\"\n  offers={{ price: 0, priceCurrency: \"USD\" }}\n  aggregateRating={{ ratingValue: 4.8, reviewCount: 500 }}\n/>;\n```\n\n### ServiceJsonLd\n\n```tsx\nimport { ServiceJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ServiceJsonLd\n  name=\"Web Development\"\n  description=\"Custom web applications\"\n  url=\"https://example.com/services/web\"\n  provider=\"My Company\"\n  providerUrl=\"https://example.com\"\n/>;\n```\n\n### OrganizationJsonLd\n\n```tsx\nimport { OrganizationJsonLd } from \"@bliztek/seo/json-ld\";\n\n<OrganizationJsonLd\n  name=\"My Company\"\n  url=\"https://example.com\"\n  logo=\"/logo.png\"\n  description=\"We build software.\"\n  sameAs={[\"https://twitter.com/mycompany\"]}\n/>;\n```\n\n### LocalBusinessJsonLd\n\n```tsx\nimport { LocalBusinessJsonLd } from \"@bliztek/seo/json-ld\";\n\n<LocalBusinessJsonLd\n  name=\"My Company\"\n  url=\"https://example.com\"\n  logo=\"/logo.png\"\n  description=\"Local software consultancy.\"\n  telephone=\"+1-555-555-5555\"\n  email=\"hello@example.com\"\n  address={{\n    locality: \"Orlando\",\n    region: \"FL\",\n    country: \"US\",\n  }}\n  areaServed={[\n    { type: \"City\", name: \"Orlando\", containedIn: \"Florida\" },\n  ]}\n  sameAs={[\"https://twitter.com/mycompany\"]}\n/>;\n```\n\n### ItemListJsonLd\n\nFor carousels and collection pages:\n\n```tsx\nimport { ItemListJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ItemListJsonLd\n  items={[\n    { url: \"https://example.com/item-1\", name: \"Item 1\" },\n    { url: \"https://example.com/item-2\", name: \"Item 2\" },\n    { url: \"https://example.com/item-3\", name: \"Item 3\" },\n  ]}\n  itemListOrder=\"ascending\"\n/>;\n```\n\n### GenericJsonLd\n\nEscape hatch for any Schema.org type not covered by the typed components:\n\n```tsx\nimport { GenericJsonLd } from \"@bliztek/seo/json-ld\";\n\n<GenericJsonLd\n  type=\"MedicalCondition\"\n  data={{\n    name: \"Common Cold\",\n    description: \"A viral infectious disease of the upper respiratory tract\",\n    possibleTreatment: { \"@type\": \"MedicalTherapy\", name: \"Rest and fluids\" },\n  }}\n/>;\n```\n\n## Sitemap Helpers\n\nUtilities that return objects compatible with Next.js `sitemap.ts`:\n\n```ts\n// app/sitemap.ts\nimport { generateSitemapEntry, generateSitemap } from \"@bliztek/seo\";\nimport type { MetadataRoute } from \"next\";\n\nexport default function sitemap(): MetadataRoute.Sitemap {\n  return generateSitemap([\n    { url: \"https://example.com\", options: { priority: 1.0, changeFrequency: \"daily\" } },\n    { url: \"https://example.com/about\", options: { priority: 0.8 } },\n    { url: \"https://example.com/blog\", options: { changeFrequency: \"weekly\" } },\n  ]);\n}\n```\n\nPriority is validated to be between 0 and 1. `lastModified` accepts both `Date` objects and ISO date strings.\n\n## Robots.txt Helpers\n\nGenerate a robots.txt config compatible with Next.js `robots.ts`:\n\n```ts\n// app/robots.ts\nimport { generateRobots } from \"@bliztek/seo\";\nimport type { MetadataRoute } from \"next\";\n\nexport default function robots(): MetadataRoute.Robots {\n  return generateRobots({\n    siteUrl: \"https://example.com\",\n    sitemapUrl: \"/sitemap.xml\",\n    rules: [\n      { userAgent: \"*\", allow: \"/\", disallow: \"/admin\" },\n      { userAgent: \"Googlebot\", allow: \"/\" },\n    ],\n  });\n}\n\n// For staging/preview environments:\nexport default function robots(): MetadataRoute.Robots {\n  return generateRobots({\n    siteUrl: \"https://staging.example.com\",\n    disallowAll: true,\n  });\n}\n```\n\n## SEO Validation\n\n`validateSEO` is a standalone, pure function that returns warnings without side effects. Use it in dev mode, tests, or CI:\n\n```ts\nimport { validateSEO } from \"@bliztek/seo\";\n\nconst warnings = validateSEO(\n  {\n    title: \"This is a very long title that exceeds sixty characters and will be truncated in search results\",\n    description: \"Short\",\n    url: \"/page\",\n  },\n  siteConfig\n);\n\n// warnings = [\n//   { field: \"title\", severity: \"warning\", message: \"Title is 93 characters (recommended: 60 or fewer)\", value: 93 },\n//   { field: \"description\", severity: \"warning\", message: \"Description is 5 characters (recommended: 50 or more)\", value: 5 },\n// ]\n\n// Use in tests:\nimport { expect, it } from \"vitest\";\n\nit(\"has valid SEO metadata\", () => {\n  const warnings = validateSEO(seoProps, siteConfig);\n  const errors = warnings.filter((w) => w.severity === \"error\");\n  expect(errors).toHaveLength(0);\n});\n```\n\n**Validation rules:**\n\n| Rule | Severity | Condition |\n|---|---|---|\n| Title empty | error | No title |\n| Title too long | warning | > 60 characters |\n| Description empty | error | No description |\n| Description too long | warning | > 160 characters |\n| Description too short | warning | < 50 characters |\n| Missing OG image | warning | No image and no default |\n| No URL or canonical | info | Both empty |\n| URL missing slash | warning | Doesn't start with `/` |\n| Too many keywords | info | > 10 keywords |\n\n## LLM / AI Search Optimization\n\nThe `@bliztek/seo/ai` entry point provides tools for optimizing your site for AI-powered search engines (ChatGPT, Perplexity, Google AI Overviews, etc.).\n\n### llms.txt Generation\n\nGenerate a [llms.txt](https://llmstxt.org/) file that helps LLMs understand your site:\n\n```ts\n// app/llms.txt/route.ts\nimport { generateLlmsTxt } from \"@bliztek/seo/ai\";\n\nexport function GET() {\n  const content = generateLlmsTxt({\n    name: \"My Company\",\n    summary: \"We build developer tools for the modern web.\",\n    sections: [\n      {\n        heading: \"Documentation\",\n        links: [\n          { name: \"Getting Started\", url: \"/docs/start\", description: \"Quick start guide\" },\n          { name: \"API Reference\", url: \"/docs/api\", description: \"Full API documentation\" },\n        ],\n      },\n      {\n        heading: \"Products\",\n        links: [\n          { name: \"Widget Pro\", url: \"/products/pro\", description: \"Our flagship product\" },\n        ],\n      },\n    ],\n    optional: [\n      { name: \"Blog\", url: \"/blog\", description: \"Company blog\" },\n      { name: \"Careers\", url: \"/careers\" },\n    ],\n  });\n\n  return new Response(content, {\n    headers: { \"Content-Type\": \"text/plain; charset=utf-8\" },\n  });\n}\n```\n\n### AI Crawler Management\n\nA registry of 28+ known AI crawler user agents with metadata (company, purpose):\n\n```ts\nimport {\n  AI_CRAWLERS,\n  getTrainingCrawlers,\n  getSearchCrawlers,\n  getCrawlerUserAgents,\n} from \"@bliztek/seo/ai\";\n\n// All crawlers with metadata\nconsole.log(AI_CRAWLERS);\n// [{ userAgent: \"GPTBot\", company: \"OpenAI\", purpose: \"training\" }, ...]\n\n// Just training crawlers (GPTBot, ClaudeBot, CCBot, etc.)\nconst training = getTrainingCrawlers();\n\n// Just search/citation crawlers (OAI-SearchBot, PerplexityBot, etc.)\nconst search = getSearchCrawlers();\n\n// Get user-agent strings only (useful for custom robots.txt logic)\nconst allAgents = getCrawlerUserAgents(\"all\");\nconst trainingAgents = getCrawlerUserAgents(\"training\");\nconst searchAgents = getCrawlerUserAgents(\"search\");\n```\n\n### AI-Enhanced Robots.txt\n\nGenerate robots.txt with AI-specific presets:\n\n```ts\n// app/robots.ts\nimport { generateAIRobots } from \"@bliztek/seo/ai\";\n\nexport default function robots() {\n  return generateAIRobots({\n    siteUrl: \"https://example.com\",\n    blockAITraining: true,    // Block GPTBot, ClaudeBot, CCBot, etc.\n    allowAISearch: true,      // Allow OAI-SearchBot, PerplexityBot, etc.\n    sitemapUrl: \"/sitemap.xml\",\n    additionalRules: [\n      { userAgent: \"*\", allow: \"/\", disallow: \"/admin\" },\n    ],\n  });\n}\n\n// Block ALL AI crawlers (training + search):\ngenerateAIRobots({ siteUrl: \"https://example.com\", blockAllAI: true });\n```\n\n### JSON-LD Knowledge Graph\n\nCompose multiple schema types into a connected `@graph` with `@id` references. This is the #1 signal AI search engines use for entity disambiguation:\n\n```tsx\nimport { JsonLdGraph } from \"@bliztek/seo/ai\";\n\n<JsonLdGraph items={[\n  {\n    \"@type\": \"Organization\",\n    \"@id\": \"#org\",\n    name: \"My Company\",\n    url: \"https://mycompany.com\",\n    knowsAbout: [\"Web Development\", \"AI\", \"SEO\"],\n  },\n  {\n    \"@type\": \"WebSite\",\n    \"@id\": \"#site\",\n    name: \"My Company Blog\",\n    url: \"https://mycompany.com\",\n    publisher: { \"@id\": \"#org\" },\n  },\n  {\n    \"@type\": \"Article\",\n    headline: \"How to Optimize for AI Search\",\n    author: { \"@id\": \"#author\" },\n    publisher: { \"@id\": \"#org\" },\n  },\n]} />;\n```\n\n### Speakable Markup\n\nAdd `speakable` to Article, BlogPosting, NewsArticle, and TechArticle to flag the most citable passages for AI search:\n\n```tsx\nimport { ArticleJsonLd } from \"@bliztek/seo/json-ld\";\n\n<ArticleJsonLd\n  headline=\"How to Optimize for AI Search\"\n  description=\"A guide to getting cited by ChatGPT and Perplexity\"\n  datePublished=\"2026-01-15\"\n  url=\"https://example.com/ai-seo\"\n  author={{ name: \"Jane Doe\" }}\n  publisher={{ name: \"My Co\", url: \"https://myco.com\", logoUrl: \"/logo.png\" }}\n  speakable={[\".article-summary\", \".key-takeaways\"]}\n  mainEntityOfPage=\"https://example.com/ai-seo\"\n/>;\n```\n\n### AI-Optimized JSON-LD Components\n\nNew components specifically valuable for AI search:\n\n- **`ClaimReviewJsonLd`** — fact-checking schema; AI overviews treat these as high-trust sources\n- **`DatasetJsonLd`** — original research/data that LLMs reference\n- **`DiscussionForumPostingJsonLd`** — community content (Perplexity cites forums heavily)\n- **`TechArticleJsonLd`** — technical documentation with proficiency level and speakable\n\n```tsx\nimport {\n  ClaimReviewJsonLd,\n  DatasetJsonLd,\n  DiscussionForumPostingJsonLd,\n  TechArticleJsonLd,\n} from \"@bliztek/seo/json-ld\";\n\n<ClaimReviewJsonLd\n  url=\"https://factcheck.com/review/123\"\n  claimReviewed=\"The earth is flat\"\n  author={{ name: \"Flat Earth Society\", type: \"Organization\" }}\n  reviewRating={{ ratingValue: 1, bestRating: 5, alternateName: \"False\" }}\n  publisher={{ name: \"FactCheck.com\", url: \"https://factcheck.com\" }}\n/>;\n\n<DatasetJsonLd\n  name=\"Global Temperature Data\"\n  description=\"Monthly global temperature anomalies\"\n  creator={{ name: \"Climate Institute\", type: \"Organization\" }}\n  license=\"https://creativecommons.org/licenses/by/4.0/\"\n  temporalCoverage=\"1880/2026\"\n  distribution={[\n    { contentUrl: \"https://data.climate.org/temps.csv\", encodingFormat: \"text/csv\" },\n  ]}\n/>;\n\n<DiscussionForumPostingJsonLd\n  headline=\"How to optimize for AI search?\"\n  text=\"I'm looking for tips on getting cited by ChatGPT and Perplexity...\"\n  url=\"https://forum.com/post/123\"\n  author={{ name: \"Alice\", url: \"https://forum.com/u/alice\" }}\n  datePublished=\"2026-01-15\"\n  commentCount={12}\n  upvoteCount={42}\n  comment={[\n    { text: \"Great question! Use structured data.\", author: { name: \"Bob\" }, datePublished: \"2026-01-16\" },\n  ]}\n/>;\n\n<TechArticleJsonLd\n  headline=\"React Server Components: A Deep Dive\"\n  description=\"Everything you need to know about RSC architecture\"\n  datePublished=\"2026-01-15\"\n  url=\"https://dev.co/rsc-guide\"\n  author={{ name: \"Jane Doe\", url: \"https://janedoe.com\" }}\n  publisher={{ name: \"Dev Co\", url: \"https://dev.co\", logoUrl: \"https://dev.co/logo.png\" }}\n  proficiencyLevel=\"Expert\"\n  dependencies=\"React 19, Next.js 15\"\n  speakable={[\".article-intro\", \".key-points\"]}\n  mainEntityOfPage=\"https://dev.co/rsc-guide\"\n/>;\n```\n\n### knowsAbout (Topical Authority)\n\nAdd `knowsAbout` to Person and Organization schemas to signal topical expertise to AI search:\n\n```tsx\n<PersonJsonLd\n  name=\"Jane Doe\"\n  jobTitle=\"Senior Engineer\"\n  knowsAbout={[\"Machine Learning\", \"Next.js\", \"SEO Optimization\"]}\n  sameAs={[\"https://github.com/janedoe\", \"https://linkedin.com/in/janedoe\"]}\n/>;\n\n<OrganizationJsonLd\n  name=\"Tech Co\"\n  url=\"https://techco.com\"\n  logo=\"/logo.png\"\n  description=\"We build AI tools\"\n  knowsAbout={[\"Artificial Intelligence\", \"Cloud Infrastructure\", \"Developer Tools\"]}\n  sameAs={[\"https://en.wikipedia.org/wiki/TechCo\", \"https://www.crunchbase.com/organization/techco\"]}\n/>;\n```\n\n## Feed Generator Integration\n\nBridge your `PostMetadata` to `@bliztek/feed-generator` with zero manual mapping:\n\n```bash\npnpm add @bliztek/feed-generator\n```\n\n### Convert posts to feed items\n\n```ts\nimport { postMetadataToFeedItem } from \"@bliztek/seo\";\nimport { siteConfig } from \"./seo-config\";\n\nconst feedItems = posts.map(({ slug, metadata }) =>\n  postMetadataToFeedItem(slug, metadata, siteConfig)\n);\n```\n\n`postMetadataToFeedItem` maps `PostMetadata` fields to feed-generator's `FeedItem` shape:\n- `description` → `summary`\n- `author` (string) → `authors` (object array)\n- `category` + `keywords` → `categories` (deduplicated)\n- `dateModified` → `updated`\n- Constructs `link` from `siteConfig.url` + `blogPathPrefix` + slug (absolute `link` overrides via `options.link` are passed through)\n\n### Build a complete feed\n\n```ts\nimport { postMetadataToFeedItem, buildFeed } from \"@bliztek/seo\";\nimport { generateFeeds } from \"@bliztek/feed-generator\";\nimport { siteConfig } from \"./seo-config\";\n\nconst items = posts.map(({ slug, metadata }) =>\n  postMetadataToFeedItem(slug, metadata, siteConfig)\n);\n\nconst feed = buildFeed(siteConfig, items, {\n  description: \"Latest posts from our blog\",\n  language: \"en\",\n  copyright: `© ${new Date().getFullYear()} My Company`,\n  favicon: \"/favicon.ico\",\n  feedLinks: {\n    rss: \"/feed.xml\",\n    atom: \"/atom.xml\",\n    json: \"/feed.json\",\n  },\n});\n\nconst { rss, atom, json } = generateFeeds(feed);\n```\n\n`buildFeed` derives feed metadata from your existing `SiteConfig`:\n- `siteName` → feed title\n- `defaultAuthor` → feed author\n- `defaultImage` → feed image (joined to site URL; absolute URLs pass through unchanged)\n- Auto-sets `updated` to the most recent item date\n\n## Types\n\n**From `@bliztek/seo`:**\n\n- `SiteConfig` — Site-wide configuration (URL, title suffix, site name, Twitter handle, icons)\n- `SEOProps` — Input props for `generateSEO()` (title, description, OG type, article tags, i18n, etc.)\n- `SEO` — Shape used by `createSEO()`\n- `FAQ` — Question/answer pair for `FAQPageJsonLd`\n- `PostMetadata` — Blog post metadata for `blogPostToSEOProps()`\n- `BlogPostingAuthor` — Author object for blog/article components\n- `BlogPostingPublisher` — Publisher object for blog/article components\n- `ArticleOG` — Article-specific Open Graph metadata\n- `OGType` — Open Graph type union\n- `SEOWarning` — Validation warning returned by `validateSEO()`\n- `SitemapEntryOptions` — Options for `generateSitemapEntry()`\n- `RobotsConfig` — Configuration for `generateRobots()`\n- `RobotsRule` — Individual rule for robots.txt\n- `FeedItemOutput` — Feed item shape compatible with `@bliztek/feed-generator`\n- `FeedItemOptions` — Options for `postMetadataToFeedItem()`\n- `FeedOutput` — Complete feed shape compatible with `@bliztek/feed-generator`\n- `BuildFeedOptions` — Options for `buildFeed()`\n\n**From `@bliztek/seo/ai`:**\n\n- `AICrawler` — AI crawler entry with userAgent, company, and purpose\n- `CrawlerPurpose` — `\"training\" | \"search\" | \"both\"`\n- `LlmsTxtConfig` — Configuration for `generateLlmsTxt()`\n- `LlmsTxtSection` — Section with heading and links for llms.txt\n- `LlmsTxtLink` — Link entry for llms.txt (name, url, description)\n- `AIRobotsConfig` — Configuration for `generateAIRobots()`\n\n## Security\n\nAll JSON-LD components use `safeJsonLd()` to prevent XSS via `</script>` injection in structured data. The `<` character is escaped to `\\u003c` in all serialized output.\n\n## Migration\n\n### From 2.1.0 to 2.1.1\n\nBug-fix release. No breaking type changes. Two behavioral notes:\n\n- **`OrganizationJsonLd` / `LocalBusinessJsonLd`: relative `contactUrl` is now joined to `url`.** Previously a relative `contactUrl` like `/help` was emitted verbatim into the schema (Schema.org-invalid); it now resolves to `${url}/help`. Absolute `contactUrl` values are unchanged.\n- **All URL-shaped props across the public API now accept absolute, protocol-relative, or relative inputs interchangeably.** Spot-check any of these if you pass them: `OrganizationJsonLd.{logo,contactUrl}`, `LocalBusinessJsonLd.{logo,contactUrl}`, `BlogPostingJsonLd` (built from `siteUrl` + `blogPathPrefix` + `slug`), `generateSEO` (`og:url`, canonical), `buildFeed` (`image`, `favicon`, `feedLinks`), `postMetadataToFeedItem` (`link`). OG/Twitter images and icons are also handled correctly — via the `metadataBase` fix above rather than `joinUrl` directly: `generateSEO` passes `image` / `defaultImage` / `icons` verbatim to Next.js, which resolves relative paths against `metadataBase` (now pinned to site origin) at SSR. If you were already passing relative paths (the documented usage), output is identical. Consumers who were working around the old double-prefix bug by hand-stripping `siteConfig.url` from their inputs should remove that workaround.\n\nSee the [CHANGELOG](./CHANGELOG.md) for the full bug-fix list.\n\n### From v1 to v2\n\nv2 is backward compatible with one behavioral change:\n\n- **`blogPostToSEOProps` now sets `ogType: \"article\"`** — Blog posts will output `og:type=article` instead of `og:type=website`. This is the correct behavior per the Open Graph spec. If you need the old behavior, pass `{ ogType: \"website\" }` in the `defaults` parameter.\n\nAll new features use optional fields — existing code compiles and runs without changes.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}