OpinlySDK

SEO & structured data

Generate metadata and schema.org JSON-LD from resolved content, and feed it into your framework's head.

SEO has two parts: metadata (title, description, canonical, Open Graph) and structured data (schema.org JSON-LD). @opinly/shared builds both in a framework-neutral form; the meta-adapters reshape them for your framework.

Metadata builders (@opinly/shared)

import { buildMetadata } from '@opinly/shared'

const meta = buildMetadata(resolved, config)
// → { title, description?, canonicalUrl?, ogImage?, ogType?, authors?, publishedTime?, modifiedTime? }

buildMetadata takes a resolved route (a SeoResolved — a { type, data } tagged with its kind, e.g. { type: 'post', data: await opinly.post(slug) } or the result of opinly.author()) plus your OpinlyConfig and returns neutral OpinlyMeta. Map that onto whatever head API your framework uses.

JSON-LD builders (@opinly/shared)

Each returns a plain schema.org object (@context included) ready to serialize:

BuilderSchema
buildBlogPostingJsonLd(post, config)BlogPosting
buildFaqJsonLd(faqs)FAQPage
buildBreadcrumbJsonLd(items)BreadcrumbList
buildPersonJsonLd(author, config)Person
buildCollectionJsonLd(collection)CollectionPage

Per-framework adapters

@opinly/next gives you a ready Next Metadata object and a JSON-LD <script>:

import { generateOpinlyMetadata, OpinlyJsonLd, buildBlogPostingJsonLd } from '@opinly/next'

// metadata for the route — pass the already-resolved data (no second fetch):
export const generateMetadata = async (props, parent) =>
  generateOpinlyMetadata(await loadRoute(slug), parent)

// structured data in the page:
<OpinlyJsonLd data={buildBlogPostingJsonLd(post)} />

@opinly/nuxt's opinlyHead() returns a useHead() payload (title, meta, JSON-LD script):

import { opinlyHead, buildBlogPostingJsonLd } from '@opinly/nuxt'

useHead(
  opinlyHead({
    resolved,
    config,
    jsonLd: [buildBlogPostingJsonLd(resolved.data, config)],
  }),
)

@opinly/sveltekit's <OpinlySeo> writes into <svelte:head>:

<script>
  import { OpinlySeo } from '@opinly/sveltekit'
</script>

<OpinlySeo resolved={data.resolved} {config} jsonLd={[/* … */]} />

No dedicated adapter — use the neutral builders with your head management (React Helmet, Remix meta, @vueuse/head, …):

import { buildMetadata, buildBlogPostingJsonLd } from '@opinly/shared'

const meta = buildMetadata(resolved, config)
const jsonLd = buildBlogPostingJsonLd(post, config)
// render <title>, <meta>, and <script type="application/ld+json"> yourself

Sitemap & RSS

Fetch every addressable route once with opinly.routes(), then shape entries with @opinly/shared's buildSitemapEntries / toSitemapXml (or sitemapUrl / routeParams per route) — posts flat, categories/authors prefixed per your config. opinly.rss() covers the feed. See the framework guides for full route examples.