SvelteKit
Render your Opinly blog in SvelteKit — server load functions, Svelte rendering, and SEO via the OpinlySeo component.
In SvelteKit you fetch content in a server load function, render it with @opinly/svelte, and
add SEO with @opinly/sveltekit's <OpinlySeo> component (which writes into <svelte:head>).
Install
pnpm add @opinly/backend @opinly/svelte @opinly/sveltekit @opinly/sharednpm i @opinly/backend @opinly/svelte @opinly/sveltekit @opinly/sharedyarn add @opinly/backend @opinly/svelte @opinly/sveltekit @opinly/sharedSet OPINLY_API_KEY in your environment. Fetching in +page.server.ts keeps it server-only.
Fetch in a server load
Posts are flat, so a post lives at a single-segment route ([slug]). The index,
category archives, and authors get their own routes (/blog/+page.server.ts,
/blog/category/[slug], /blog/authors/[...]) calling posts()/categories()/
author() — params.slug here is always one segment.
// src/routes/blog/[slug]/+page.server.ts
import { createOpinlyClient } from '@opinly/backend'
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ params }) => {
const opinly = createOpinlyClient({ apiKey: process.env.OPINLY_API_KEY })
const post = await opinly.post(params.slug) // flat, single-segment post slug
const resolved = post ? { type: 'post' as const, data: post } : { type: 'not-found' as const }
return { resolved }
}Migrating from category-nested URLs? Add a
[category]/[slug]route whose server load callsredirect(308, ...)to the flat post path, so old links resolve.
Render + SEO
<!-- src/routes/blog/[...slug]/+page.svelte -->
<script lang="ts">
import { OpinlyContent } from '@opinly/svelte'
import { OpinlySeo } from '@opinly/sveltekit'
import type { OpinlyNode } from '@opinly/shared'
let { data } = $props()
const config = {
imagesPrefix: 'https://cdn.opinly.ai/REPLACE-ME-xxxxxxxx', // your 21-char CDN namespace
siteUrl: 'https://example.com',
blogPrefix: '/blog',
siteName: 'Acme',
}
</script>
<OpinlySeo resolved={data.resolved} {config} />
{#if data.resolved.type === 'post'}
<article class="prose">
<h1>{data.resolved.data.title}</h1>
<OpinlyContent content={data.resolved.data.content as OpinlyNode} {config} />
</article>
{/if}Images
Point imagesPrefix straight at the CDN (https://cdn.opinly.ai/<your-namespace>) — images load
directly from the absolute URL, with no proxy or build config needed. If you'd rather serve them
same-origin (e.g. for caching under your domain), add a platform rewrite — Vercel/Netlify
rewrites, or a handle hook in src/hooks.server.ts that proxies /images/* to the CDN — and
set imagesPrefix: '/images' to match.
<OpinlySeo resolved config jsonLd?> renders title, meta/OG tags, and any JSON-LD you pass into
<svelte:head>. <OpinlyContent> renders the body via renderToHtml injected with {@html}.
Notes
- Works with Svelte 4 and Svelte 5 (runes).
- Style the body with your own classes or the
classNamesprop — see Rendering.