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:
| Builder | Schema |
|---|---|
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"> yourselfSitemap & 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.