Last Updated: 2026-05-06 Status: Active
Paste-ready prompt for AI coding assistants (Claude / Cursor / Copilot / GPT) to integrate @vlozi/blog into a React or Next.js application without losing styling, breaking SEO, or shipping a half-working integration. Target SDK version: @vlozi/blog@2.1.6+.
NOTE
Every claim in this prompt is grounded in sdk-reference.md. When in doubt, that doc is the ground truth.
1. How to use this document
- Copy Section A verbatim into your AI assistant.
- Replace
YOUR_API_KEYandYOUR_GATEWAY_URLwith the real values. - After the agent finishes the integration, run Section B's verification fixture before considering the integration done.
- If anything fails, jump to Section D — Troubleshooting.
2. Section A — The integration prompt
Copy everything between the
=====lines into your AI assistant.
=====================================================================
## Task
Integrate the `@vlozi/blog` SDK (^2.1.6) into my React/Next.js application.
Build a working blog with `/blog` (list), `/blog/[slug]` (post detail),
`/blog/category/[slug]`, `/blog/tag/[slug]`, and an RSS feed.
## STEP 1 — Analyze my codebase first (mandatory before writing code)
Output your findings BEFORE writing any code so I can verify you understood
my codebase. Cover:
1. **Framework**: Next.js App Router? Pages Router? Astro? Vite + React Router?
2. **Design system**: tailwind.config.*, globals.css, CSS-vars file. Is it
shadcn-style HSL tokens? Tailwind v4 with @theme? Plain CSS vars? List
my color tokens, fonts, border-radius, spacing.
3. **Component library**: shadcn? MUI? Custom? List Button, Input, Card,
Skeleton — whatever I have.
4. **Layout**: shared chrome (header, footer, container width, padding).
5. **Typography**: font family, heading sizes, body color, muted color.
6. **Routing**: file-based or programmatic.
7. **Theming**: dark mode — next-themes? data-theme attr? media query?
8. **Build mode**: dev / static export / SSR.
## STEP 2 — The four hard rules (violating these breaks the integration)
These are the most common failure modes I see in real consumer integrations.
DO NOT skip them.
### Rule 1 — Import `@vlozi/blog/styles.css` FIRST
```tsx
// app/layout.tsx (Next.js App Router)
import "@vlozi/blog/styles.css"; // ← MUST be first
import "./globals.css"; // ← my own styles secondThe SDK's CSS targets .vlz-content and .vlz-* selectors. Importing
your 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.
If your stack reverses the order anyway (some monorepo bundlers do),
scope all your overrides under .vlz-content so they have higher
specificity than the unscoped SDK rules.
Rule 2 — DO NOT add prose classes to <BlogContent>
The SDK ships its own prose baseline. The Tailwind Typography plugin's
prose / prose-lg / dark:prose-invert classes are a parallel
stylesheet that fights 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 — DO NOT style bare <pre> or <iframe> in your global CSS
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 my code blocks but not mermaid hosts */
.vlz-content pre:not([data-vlz-hydrated]) {
background: var(--my-code-bg);
}
/* Style my non-SDK iframes (allowlisted YouTube embeds keep the SDK
styling automatically; everything else is stripped before render) */
iframe:not([src*="youtube-nocookie.com"]):not([src*="youtube.com/embed"]) {
/* whatever you want */
}Rule 4 — Use slug as the React key, not id
The Post type has NO id field. The public API never returns one.
{posts.map((post) => (
<li key={post.slug}>{post.title}</li> // ✅
))}
{posts.map((post) => (
<li key={post.id}>{post.title}</li> // ❌ TypeScript error
))}STEP 3 — Install + environment
pnpm add @vlozi/blog
# If your authors publish posts containing mermaid diagrams:
pnpm add mermaidRequired environment variables:
# Server-side use (Server Components, generateMetadata, RSS routes,
# blog-service calls). PREFER THIS — your API key never reaches the
# client bundle.
VLOZI_API_KEY=YOUR_API_KEY
VLOZI_BASE_URL=YOUR_GATEWAY_URL
# Only if you also need a browser-side client (interactive search,
# infinite scroll, etc.):
NEXT_PUBLIC_VLOZI_API_KEY=YOUR_API_KEY
NEXT_PUBLIC_VLOZI_BASE_URL=YOUR_GATEWAY_URLThe API key is a public read-only pk_* key — exposing it to the client
is safe but not necessary for purely server-rendered blog routes.
STEP 4 — File structure (create these files)
lib/vlozi.ts — shared module-scope client (server-side)
import { VloziClient } from "@vlozi/blog";
export const blogClient = new VloziClient({
apiKey: process.env.VLOZI_API_KEY ?? "",
baseUrl: process.env.VLOZI_BASE_URL ?? "",
});
export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://example.com";The constructor does NOT throw on empty values (2.1.5+); the typed
VloziConfigError fires on the first API call. So this module imports
cleanly during next build even when env vars aren't yet wired.
app/layout.tsx — root layout: SDK CSS first, then yours
import "@vlozi/blog/styles.css"; // FIRST — Rule 1
import "./globals.css"; // SECOND
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html lang="en"><body>{children}</body></html>;
}Map your design tokens onto the five --vlz-* knobs in your
globals.css AFTER importing the SDK CSS:
/* shadcn-style HSL */
.vlz-content {
--vlz-accent: hsl(var(--primary));
--vlz-muted-fg: hsl(var(--muted-foreground));
--vlz-border: hsl(var(--border));
--vlz-surface: hsl(var(--muted) / 0.6);
--vlz-surface-hover: hsl(var(--muted) / 0.8);
}
/* OR Tailwind v4 with @theme */
.vlz-content {
--vlz-accent: var(--color-primary);
--vlz-muted-fg: var(--color-muted-foreground);
--vlz-border: var(--color-border);
--vlz-surface: var(--color-muted);
--vlz-surface-hover: var(--color-accent);
}next.config.js — featured-image domains
module.exports = {
images: {
remotePatterns: [
{ protocol: "https", hostname: "media.vlozi.app" },
// add other hosts your authors actually use
],
},
};app/blog/page.tsx — list page (server-rendered, zero JS)
import { ServerBlogList } from "@vlozi/blog/server";
import { blogClient } from "@/lib/vlozi";
export const dynamic = "force-dynamic"; // or revalidate = 60 for ISR
export default function BlogIndexPage() {
return (
<main className="my-page-container">
<h1 className="my-page-title">Blog</h1>
<ServerBlogList client={blogClient} limit={9} />
</main>
);
}If you want interactive search/filters: build it as a client component
using usePosts from @vlozi/blog/react, wrapped in a <VloziProvider>
mounted in app/blog/layout.tsx.
app/blog/[slug]/page.tsx — post detail
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { VloziApiError } from "@vlozi/blog";
import { BlogContent } from "@vlozi/blog/react";
import { generateMetadataForPost, generateStaticParamsForPosts } from "@vlozi/blog/next";
import { blogClient, SITE_URL } from "@/lib/vlozi";
type PageProps = { params: Promise<{ slug: string }> };
export async function generateStaticParams() {
return generateStaticParamsForPosts({ client: blogClient });
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
return generateMetadataForPost({ client: blogClient, slug, siteUrl: SITE_URL });
}
export default async function PostPage({ params }: PageProps) {
const { slug } = await params;
let post;
try {
post = await blogClient.blog.get(slug);
} catch (err) {
if (err instanceof VloziApiError && err.status === 404) notFound();
throw err;
}
return (
<article className="my-post-layout">
{post.featuredImageUrl && (
<div className="my-post-banner">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={post.featuredImageUrl} alt={post.title} />
</div>
)}
<header className="my-post-header">
{post.category && <span className="my-pill">{post.category.name}</span>}
<h1>{post.title}</h1>
{post.excerpt && <p className="my-lede">{post.excerpt}</p>}
</header>
{/* Body — sanitized, hydrated, syntax-highlighted */}
{post.content && <BlogContent html={post.content} />}
{post.tags && post.tags.length > 0 && (
<footer className="my-post-tags">
{post.tags.map((t) => <span key={t.slug}>#{t.name}</span>)}
</footer>
)}
</article>
);
}STEP 5 — When to use <VloziProvider> (client-side hooks/components only)
// app/blog/layout.tsx — wrap ONLY the routes that need hooks
"use client";
import { useMemo } from "react";
import { VloziClient } from "@vlozi/blog";
import { VloziProvider } from "@vlozi/blog/react";
export default function BlogLayout({ children }: { children: React.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", // or "auto" / "dark" / a function
syntaxHighlighting: true, // false to opt out (then ship your own .hljs theme)
// imageComponent: NextImage, // optional — upgrades inline body images
}}
>
{children}
</VloziProvider>
);
}Server-side fetching (generateMetadata, generateStaticParams,
<ServerBlogList>, <ServerBlogPost>, RSS routes) does NOT need a
provider — it uses the module-scope blogClient directly.
Mounting a provider when you only need server-rendering is dead weight.
STEP 6 — Critical gotchas (read once, save 4 hours)
-
<BlogContent transformHtml={...}>is your escape hatch when the server-rendered HTML has bugs. Use it sparingly and only as a workaround until the upstream fix deploys. Output is still sanitized — not an XSS escape. -
YouTube embeds: only
youtube-nocookie.com/andyoutube.com/embed/are allowed by the iframe sanitizer. If you see an empty 16:9 box where a video should be, check the iframe'ssrc. -
Mermaid: lazy-loaded only when a post contains a mermaid block. Install
mermaidif your authors use it. -
<BlogCategoryNav onSelect>receivesnullfor the "All" button. Convert with:onSelect={(slug) => setCategory(slug ?? undefined)}. -
post.content is HTML and only on the detail endpoint —
client.blog.list()returns posts WITHOUT content (excerpt only). -
post.iddoes not exist — the API never returns it. Usepost.slugas your React key. -
Search debounce: 300ms is the SDK default in
<BlogList searchable>. If you build your own search, debounce at 300ms — the API is rate-limited.
STEP 7 — Match my design
- DO match my color scheme via the
--vlz-*knob mapping (see Step 4) - DO match my container width, header/footer chrome on the blog pages
- DO use my existing Card / Button / Skeleton components for custom
list pages built with
usePosts(instead of the prebuilt<BlogList>/<BlogCard>) - DO support my dark mode mechanism via
mermaidTheme(function form fornext-themes/data-themeintegrations)
Output your codebase analysis from Step 1, then implement the integration.
=====================================================================
---
## 3. Section B — Verification fixture (run AFTER integration)
After the agent finishes, **render this test post** and visually confirm
each construct displays correctly. Most styling failures are caught here.
### Step B1 — Create a published test post
Authoring path: paste the markdown below into your editor (via the
`/dashboard` admin's "Import Markdown" or a direct API call, depending
on your tooling) and publish it.
```markdown
# Heading 1 — should be the largest
A paragraph with **bold**, *italic*, ***bold-italic***, ~~strikethrough~~,
and `inline code`. A [link](https://example.com).
## Heading 2 — should be visibly smaller than H1
### Heading 3
#### Heading 4
A long paragraph to verify body line-height and column rhythm. Lorem ipsum
dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
- Bullet list item 1
- Bullet list item 2
- Bullet list item 3
1. Ordered list item 1
2. Ordered list item 2
3. Ordered list item 3
- [ ] Task item unchecked
- [x] Task item checked
> A blockquote with **bold** text inside.
```typescript
const x: string = "syntax-highlighted code block";
function greet(user: User): string {
return `Hello, ${user.name}!`;
}
```
| Header A | Header B |
| --- | --- |
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |

```mermaid
flowchart LR
A[Author] --> B[Editor]
B --> C[Published]
```
https://www.youtube.com/watch?v=dQw4w9WgXcQ
> [!NOTE]
> A callout block — should render with a subtle accent border.Step B2 — Visual checklist
Visit the rendered page and confirm:
| Construct | Expected | If broken, see... |
|---|---|---|
# H1 is biggest, ## H2 smaller, ### H3 smaller still |
Each heading has visible margin-top, distinct font-size | Section D row 1 |
**bold** is bolder, *italic* is italicized |
Inline marks render distinctly | Section D row 2 |
Inline code has rounded grey background |
The --vlz-surface token is applied |
Section D row 1 |
Bullets visible on <ul>, numbers on <ol> |
List markers appear, indent visible | Section D row 1 |
| Task items show a checkbox | Disabled checkboxes, label aligned with checkbox | Section D row 3 |
| Blockquote has accent left-border | --vlz-accent color visible |
Section D row 1 |
| Code block tokens are colored | Keywords blue, strings purple-ish, comments italic-grey | Section D row 4 |
| Table rows have borders, header has subtle bg | --vlz-border + --vlz-surface applied |
Section D row 1 |
Inline body image renders (or upgrades to your imageComponent) |
Picture visible with rounded corners | Section D row 5 |
| Mermaid block renders as an SVG diagram (NOT raw text) | Live flowchart LR rendered |
Section D row 6 |
| YouTube link on its own line embeds (not 16:9 empty box) | iframe with the video | Section D row 7 |
| Callout shows accent left-border + tinted background | border-color: #3b82f6 etc. |
Section D row 1 |
If every row checks out, the integration is correct. Ship it.
4. Section C — Customization recipes (paste as needed)
C1 — Custom <BlogList> with my own card component
Don't use <BlogCard>. Use usePosts() directly and render with my existing
Card component — same image aspect ratio, same typography, same spacing as
the rest of my app. Show: featured image, category pill, title, excerpt
(clamp 2 lines), author + date + reading time, tag pills.C2 — Custom post page with <BlogContent>
For /blog/[slug] don't use <BlogPost>. Build my own header (matching my
site's article style) and use <BlogContent html={post.content!} /> for
the body. <BlogContent> handles sanitization plus rich-block hydration —
I just provide the layout chrome around it.C3 — Headless integration (no SDK components)
Don't use any prebuilt SDK components. Use only the hooks (usePosts,
usePost, useCategories, useTags, useArchive, useRelatedPosts, useNeighbors)
and build all UI from my existing components. The only render-side I want
to keep is <BlogContent> for post bodies.C4 — Dark mode + mermaid via next-themes
"use client";
import { useTheme } from "next-themes";
import { VloziClient } from "@vlozi/blog";
import { VloziProvider } from "@vlozi/blog/react";
const client = new VloziClient({ apiKey: ..., baseUrl: ... });
export function BlogProvider({ children }: { children: React.ReactNode }) {
const { resolvedTheme } = useTheme();
return (
<VloziProvider
client={client}
config={{
mermaidTheme: () => (resolvedTheme === "dark" ? "dark" : "default"),
}}
>
{children}
</VloziProvider>
);
}C5 — RSS + Sitemap
// app/feed.xml/route.ts
import { generateRSS } from "@vlozi/blog";
import { blogClient, SITE_URL } from "@/lib/vlozi";
export async function GET() {
const xml = await generateRSS({
client: blogClient,
siteUrl: SITE_URL,
title: "My Blog",
description: "Latest posts",
limit: 50,
});
return new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8" } });
}// app/sitemap.xml/route.ts
import { generateSitemap } from "@vlozi/blog";
import { blogClient, SITE_URL } from "@/lib/vlozi";
export async function GET() {
const xml = await generateSitemap({
client: blogClient,
siteUrl: SITE_URL,
additionalUrls: [{ loc: SITE_URL + "/", priority: 1.0, changefreq: "daily" }],
});
return new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8" } });
}C6 — Inline body image upgrade with Next.js Image
"use client";
import Image from "next/image";
import { BlogContent, type VloziImageComponent } from "@vlozi/blog/react";
const NextBlogImage: VloziImageComponent = ({ src, alt, width, height }) => (
<Image
src={src}
alt={alt}
width={width ?? 1200}
height={height ?? 800}
style={{ width: "100%", height: "auto" }}
/>
);
<BlogContent html={post.content!} imageComponent={NextBlogImage} />;Per-image opt-out by adding data-vlz-skip-hydrate to a specific <img>
in the source HTML.
C7 — Skeleton primitives for custom UIs
import {
BlogPostSkeleton,
BlogCardSkeleton,
BlogListSkeleton,
} from "@vlozi/blog/react";
if (loading) return <BlogPostSkeleton />;
// or
<BlogListSkeleton columns={3} count={6} />;5. Section D — Troubleshooting
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Headings render as body text. No list markers. Body looks like a wall of unformatted text. Title above <BlogContent> looks fine, but the body inside is flat. |
Most common: @vlozi/blog/styles.css is imported AFTER your globals.css OR you have a global heading reset (Tailwind preflight, * { margin: 0 }, shadcn text-base cascading) that's overriding the SDK's .vlz-content h1/h2/h3... rules. |
(a) Move import "@vlozi/blog/styles.css" to the FIRST line of app/layout.tsx. (b) DevTools → inspect a heading → check the Computed tab → if SDK's rule is crossed-out, scope your reset under :not(.vlz-content) or move the import earlier. (c) If using Tailwind: add @layer base for your reset so unscoped SDK CSS still wins. |
| 2 | Bold/italic don't render | <BlogContent> not used (you're using raw dangerouslySetInnerHTML) — the .vlz-content class isn't on the wrapper, so styles don't apply. |
Always use <BlogContent html={post.content!} /> for post bodies — it adds the .vlz-content wrapper plus sanitization + hydration. |
| 3 | Task list checkboxes are out of alignment with their labels | Pre-2.1.6 bug. Upgrade. | pnpm add @vlozi/blog@^2.1.6 |
| 4 | Code blocks render in a single color (no syntax highlighting) | Either: (a) syntaxHighlighting: false in provider config; (b) blog-service Worker isn't running 2.1.6 yet (server-side tokenization is part of the Worker deploy, not the npm publish). |
(a) Remove the false flag or default to true; (b) deploy the blog-service Worker with wrangler deploy. |
| 5 | Inline body images render as plain <img> even though I passed imageComponent |
imageComponent not threaded into <BlogContent> — check your prop. Body-image hydration is only triggered when imageComponent is provided either as a prop or via provider config. |
Pass imageComponent explicitly: <BlogContent imageComponent={NextImage} />. Or set it on the provider: <VloziProvider config={{ imageComponent: NextImage }}>. |
| 6 | Mermaid blocks render as raw flowchart LR ... text inside a beige box |
Mermaid not installed, OR you're on @vlozi/blog@2.1.5 (regression). |
(a) pnpm add mermaid; (b) upgrade to @vlozi/blog@^2.1.6. |
| 7 | YouTube embed shows as an empty 16:9 box (no video) | The renderer emitted a non-allowlisted iframe src (e.g. youtube.com/watch?v=...) and the SDK's sanitizer stripped it. Pre-2.1.6: also strips the wrapper. |
(a) Deploy the blog-service Worker (URL normalization is in the Worker, not the npm package); (b) for instant fix: use <BlogContent transformHtml={...}> to rewrite watch URLs client-side. |
| 8 | Carousels render as a flat stack of images instead of a swipeable carousel | Known bug — the blog-service renderer doesn't emit the <div data-type="carousel"> wrapper. The SDK has the carousel runtime but never sees the marker. |
Tracked in sdk-backlog.md. Until the renderer fix deploys, build your own carousel using <Carousel> from @vlozi/blog/react for posts that need this. |
| 9 | TypeScript error: Property id does not exist on type Post |
Pre-2.1.6 the SDK had id?: string in the type but the API never emitted it. Removed in 2.1.6. |
Use post.slug as your React key. |
| 10 | Build fails with Module not found: 'mermaid' |
Pre-2.1.6 had a bundler-resolution bug. | Upgrade to @vlozi/blog@^2.1.6. |
| 11 | next build crashes with apiKey is required |
Pre-2.1.5 the constructor threw synchronously on empty key. | Upgrade to @vlozi/blog@^2.1.5. |
| 12 | "useVlozi must be used within a VloziProvider" | Component using a hook isn't wrapped in <VloziProvider>. |
Mount <VloziProvider client={client}> at the route layout level. Server-side fetching does NOT need a provider — only hooks/client-components do. |
| 13 | Featured images 404 in production | next.config.js images.remotePatterns doesn't include the upload host. |
Add the actual upload host to remotePatterns. |
| 14 | Two different VloziConfig shapes show up in IDE autocomplete |
Naming collision: @vlozi/blog exports VloziConfig (client config) AND @vlozi/blog/react exports VloziConfig (provider config). |
TypeScript resolves them by import path automatically. To use both in one file, alias one: import type { VloziConfig as VloziProviderConfig } from "@vlozi/blog/react". |
| 15 | pre { background: ... } styles a mermaid host the wrong color |
Mermaid is portal-mounted into the <pre>. Your global CSS still applies. |
Scope your <pre> rules: .vlz-content pre:not([data-vlz-hydrated]) { background: ... }. |
6. Section E — One-line prompts (copy as-is)
Minimal blog (server-rendered, zero JS, App Router)
Install @vlozi/blog (^2.1.6). Create /blog with a 3-column post grid using
ServerBlogList, and /blog/[slug] using server-side blogClient.blog.get(slug)
+ <BlogContent html={post.content!} />. Use my existing layout, color tokens,
fonts. Use `dynamic = "force-dynamic"` on both routes. Import
"@vlozi/blog/styles.css" FIRST in root layout (before globals.css). Map my
shadcn HSL tokens to --vlz-accent / --vlz-muted-fg / --vlz-border /
--vlz-surface / --vlz-surface-hover under .vlz-content. API key:
YOUR_API_KEY, baseUrl: YOUR_GATEWAY_URL.Full-featured blog (interactive search + filters + RSS)
Install @vlozi/blog (^2.1.6). Create:
- /blog (client component): debounced search input, BlogCategoryNav (sidebar
variant), BlogTagNav (cloud), 3-col grid using my own Card component
+ usePosts(), pagination
- /blog/[slug] (server component): hand-rolled header matching my article
style, <BlogContent html={post.content!} />, related posts via
useRelatedPosts(), prev/next via useNeighbors()
- /blog/category/[slug], /blog/tag/[slug]: filtered list pages
- app/feed.xml/route.ts using generateRSS
- app/sitemap.xml/route.ts using generateSitemap
Map my shadcn HSL tokens to the five --vlz-* knobs under .vlz-content. Mount
BlogProvider only over /blog routes (config: mermaidTheme: "default",
syntaxHighlighting: true). Add `images.remotePatterns` for my media host.
Import "@vlozi/blog/styles.css" FIRST in root layout. API key: YOUR_API_KEY,
baseUrl: YOUR_GATEWAY_URL."Latest 3 posts" widget on homepage
Add a "Latest Posts" section to my homepage using server-side
blogClient.blog.list({ limit: 3 }). Render with my homepage card component
(NOT <BlogCard>). Match the card style, spacing, typography of the rest of
my homepage. No provider needed (server-side only). API key: YOUR_API_KEY.7. Section F — Migration
From @vlozi/blog@2.1.5 → @vlozi/blog@2.1.6
pnpm add @vlozi/blog@^2.1.6Breaking changes:
Post.idremoved — TypeScript will flagkey={post.id ?? post.slug}patterns. Replace withkey={post.slug}.<BlogContent>no longer attaches Tailwindproseclasses (was already changed in 2.1.5; restating). If you depended on it, addclassName="prose prose-lg dark:prose-invert"yourself OR remove the dependency.
Additive (non-breaking):
<BlogContent transformHtml>,<BlogContent imageComponent><BlogList search>(controlled),<BlogArchive prefetchOnHover>- New exports:
<BlogPostSkeleton>,<BlogCardSkeleton>,<BlogListSkeleton> VloziImageProps.width/height(optional)
Bug fixes (just upgrade, no code change):
- Mermaid hydration regression from 2.1.5 fixed
- YouTube embed handling (renderer + sanitizer + wrapper-strip)
- Task-list / callout / blockquote
<p>margin leak - Server-side syntax highlighting (requires blog-service Worker deploy)
From legacy @logicspike/blog
Renamed on 2026-04-12.
pnpm remove @logicspike/blog && pnpm add @vlozi/blog@^2.1.6Find/replace:
@logicspike/blog→@vlozi/blogLogicSpikeClient→VloziClientLogicSpikeProvider→VloziProvideruseLogicSpike→useVloziLOGICSPIKE_*env-var prefix →VLOZI_*
8. What this prompt deliberately doesn't cover
- Layout/theme system (
<VloziBlogSite>, named layouts) — not yet shipped. Tracked inlayouts-vision.mdfor@vlozi/blog@2.2.0. Don't reference it in current integrations. - Hosted blog at
<tenant>.vlozi.app/blog— not yet shipped. Tracked inhosted-blog-vision.md. Current integrations are React-SDK only. - Multi-provider embeds (Vimeo / Twitter / Spotify / etc.) — not supported. YouTube only.
- Author pages (
client.blog.authors.list/.get) — backend doesn't expose endpoints yet. - Draft preview mode — not implemented; published posts only.
When the SDK ships any of the above, this prompt and sdk-reference.md
will be updated together.
End of document. Ground truth: sdk-reference.md. Bug queue: sdk-backlog.md.