OpinlySDK

Nuxt

Render your Opinly blog in Nuxt 3 — server-side fetch with useAsyncData, Vue rendering, and SEO via useHead.

In Nuxt you fetch content server-side with useAsyncData, render it with @opinly/vue, and drive SEO with @opinly/nuxt (which shapes a useHead() payload).

Install

pnpm add @opinly/backend @opinly/vue @opinly/nuxt @opinly/shared
npm i @opinly/backend @opinly/vue @opinly/nuxt @opinly/shared
yarn add @opinly/backend @opinly/vue @opinly/nuxt @opinly/shared

Add your API key to the environment (OPINLY_API_KEY). The fetch runs on the server, so the key stays private.

Fetch, render, and add SEO

<script setup lang="ts">
import { createOpinlyClient } from '@opinly/backend'
import type { OpinlyNode } from '@opinly/shared'
import { OpinlyContent } from '@opinly/vue'
import { opinlyHead, buildBlogPostingJsonLd } from '@opinly/nuxt'

const route = useRoute()
const config = {
  imagesPrefix: '/images',
  siteUrl: 'https://example.com',
  blogPrefix: '/blog',
  siteName: 'Acme',
}

// Server-side fetch (runs during SSR). This renders the single-post route; for an
// index, category archive, or author route, branch on the slug and call
// posts()/categories()/author() instead.
const { data: post } = await useAsyncData('opinly', async () => {
  const opinly = createOpinlyClient({ apiKey: process.env.OPINLY_API_KEY })
  const slug = route.params.slug as string // flat, single-segment post slug
  return opinly.post(slug) // FullPost | null
})

// SEO → Nuxt useHead (title, OG tags, JSON-LD).
watchEffect(() => {
  if (post.value) {
    useHead(
      opinlyHead({
        resolved: { type: 'post', data: post.value },
        config,
        jsonLd: [buildBlogPostingJsonLd(post.value, config)],
      }),
    )
  }
})
</script>

<template>
  <main v-if="post">
    <h1>{{ post.title }}</h1>
    <OpinlyContent :content="post.content as OpinlyNode" :config="config" />
  </main>
</template>

opinlyHead({ resolved, config, jsonLd? }) returns a plain object with title, meta, and script entries — exactly the shape useHead() expects. @opinly/nuxt also re-exports the JSON-LD builders (buildBlogPostingJsonLd, buildFaqJsonLd, …) so you don't need to import @opinly/shared separately for those.

Notes

  • <OpinlyContent> renders Vue vnodes via Vue's h() — no React anywhere.
  • Style the rendered body with your own classes or the classNames prop — see Rendering.

Images

Point imagesPrefix straight at the CDN — images load directly, no proxy or build config:

const config = {
  imagesPrefix: 'https://cdn.opinly.ai/REPLACE-ME-xxxxxxxx', // your 21-char CDN namespace
  // …siteUrl, blogPrefix, siteName
}

For automatic resizing/format optimization, add @nuxt/image and render with <NuxtImg> (it has a built-in CDN image provider). Opinly serves images by fileKey, so the absolute URL works in dev and production without a same-origin rewrite.