Get your blog up and running in your Next.js application in just a few steps.
Prerequisites
- A Vlozi account (sign up at vlozi.app)
- Your public API key (prefix
pk_*) - Your Gateway URL — see Settings > API Keys
- A React project (Next.js, Vite, Remix, Astro, etc.)
Installation
Install the SDK:
# npm
npm install @vlozi/blog
# pnpm
pnpm add @vlozi/blog
# yarn
yarn add @vlozi/blog
# bun
bun add @vlozi/blogIf your authors publish posts containing mermaid diagrams, also install the optional peer dependency:
# npm
npm install mermaid
# pnpm
pnpm add mermaid
# yarn
yarn add mermaid
# bun
bun add mermaidGet your API key
Go to Settings > API Keys and create a new key with Public scope. Copy the key (it starts with pk_) and the Gateway URL shown on the same page.
Environment variables
Add these to your .env.local (or your environment of choice):
# Server-side use (preferred — your key never reaches the browser bundle)
VLOZI_API_KEY=pk_live_...
VLOZI_BASE_URL=https://api.vlozi.app
# Client-side use — only if you need browser hooks (interactive search,
# infinite scroll). The pk_* key is safe to expose, but server-only is cleaner.
NEXT_PUBLIC_VLOZI_API_KEY=pk_live_...
NEXT_PUBLIC_VLOZI_BASE_URL=https://api.vlozi.appThe four hard rules
These are the four most common ways to break the integration. Each one prevents a real customer-reported failure mode. Read them once before writing any code.
Rule 1 — Import @vlozi/blog/styles.css first
// app/layout.tsx
import "@vlozi/blog/styles.css"; // ← MUST be first
import "./globals.css"; // ← your own styles secondThe SDK's CSS targets .vlz-content and .vlz-* selectors. Importing your own globals.css first means your global resets (Tailwind preflight, custom * { margin: 0 }, shadcn's text-base cascading) clobber the SDK's defaults. Symptom: post bodies render as flat unformatted text — no heading sizes, no list markers, no spacing. This is the single most common integration failure.
Rule 2 — Don't add prose classes to <BlogContent>
The SDK ships its own prose baseline. The Tailwind Typography plugin's prose/prose-lg/dark:prose-invert classes fight the SDK's. Result: specificity wars, broken heading rhythm, weird link colors.
// ❌ Don't
<article className="prose prose-lg dark:prose-invert">
<BlogContent html={post.content} />
</article>
// ✅ Do
<article>
<BlogContent html={post.content} />
</article>Rule 3 — Don't style bare <pre> or <iframe> globally
The SDK portal-mounts <MermaidBlock> into the original <pre> and strips non-allowlisted iframes. Your pre { background: black } rule will also color the mermaid host. Use the hydration data-attributes to scope:
/* Style code blocks but not mermaid hosts */
.vlz-content pre:not([data-vlz-hydrated]) {
background: var(--my-code-bg);
}Rule 4 — Use slug as the React key, not id
The Post type has no id field. The public API never returns one. Always key your lists by post.slug — it's URL-safe, unique, and stable.
{posts.map((post) => (
<li key={post.slug}>{post.title}</li> // ✅
))}Set up the client
Create a utility file to export your client instance:
// lib/vlozi.ts
import { VloziClient } from "@vlozi/blog";
export const vlozi = new VloziClient({
apiKey: process.env.VLOZI_API_KEY ?? "",
baseUrl: process.env.VLOZI_BASE_URL ?? "",
});The constructor doesn't throw on empty values — the typed VloziConfigError fires on the first API call instead. So this module imports cleanly during next build even when env vars aren't yet wired.
Set up the provider
Only needed if you use React hooks/components. Wrap the routes that need hooks/components with VloziProvider. Server-side fetching (generateMetadata, generateStaticParams, RSC) does NOT need a provider — it uses the module-scope client directly.
// app/blog/layout.tsx
"use client";
import { VloziClient } from "@vlozi/blog";
import { VloziProvider } from "@vlozi/blog/react";
import { useMemo, ReactNode } from "react";
export default function BlogLayout({ children }: { children: ReactNode }) {
const client = useMemo(
() => new VloziClient({
apiKey: process.env.NEXT_PUBLIC_VLOZI_API_KEY!,
baseUrl: process.env.NEXT_PUBLIC_VLOZI_BASE_URL!,
}),
[]
);
return (
<VloziProvider
client={client}
config={{
mermaidTheme: "default", // "auto" | "default" | "dark" | a function
syntaxHighlighting: true,
}}
>
{children}
</VloziProvider>
);
}Render the blog list
In app/blog/page.tsx:
"use client";
import { BlogList } from "@vlozi/blog/react";
export default function BlogPage() {
return (
<div className="container py-10">
<h1 className="text-4xl font-bold mb-8">My Blog</h1>
<BlogList columns={3} showPagination />
</div>
);
}Filter by category or tag
<BlogList category="tutorials" tag="react" columns={2} />NOTE
BlogList handles data fetching on the client. For better SEO, prefer the server-side ServerBlogList from @vlozi/blog/server — see Examples.
Verify it works
Render a test post containing every construct (# H1 through ### H3, **bold**, *italic*, `code`, bullet list, ordered list, code fence, blockquote) and visually confirm each one styles distinctly.
If headings render flat as body text, you almost certainly have Rule 1 wrong — your CSS imports are out of order, or a global heading reset is winning the cascade. See Troubleshooting.