Use generateStaticParamsForPosts from @vlozi/blog/next to pre-render every published post at build time. This is the fastest possible way to serve your blog — no server-side rendering, no cold starts.
Installation
npm install @vlozi/blogSet up the client
// 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 ?? "",
});Generate static paths
// app/blog/[slug]/page.tsx
import { generateStaticParamsForPosts } from "@vlozi/blog/next";
import { vlozi } from "@/lib/vlozi";
export async function generateStaticParams() {
return generateStaticParamsForPosts({ client: vlozi });
// Returns: [{ slug: "my-first-post" }, { slug: "another-post" }, ...]
}This function paginates through your posts in 100-post batches and collects every slug. next build pre-renders one page per slug — the page fetches happen once at build time, not at request time.
Options
generateStaticParamsForPosts({
client, // required — your VloziClient
paramKey?: string, // the dynamic segment name; default "slug"
limit?: number, // hard cap on how many posts are pre-rendered; default 1000
})Posts are fetched in 100-post pages internally until limit is reached.
NOTE
For blogs larger than limit, either raise it (at the cost of build time) or
rely on ISR to cover the long tail. See ISR →.
Generate metadata
// app/blog/[slug]/page.tsx
import { generateMetadataForPost } from "@vlozi/blog/next";
import { vlozi } from "@/lib/vlozi";
export async function generateMetadata({ params }: { params: { slug: string } }) {
return generateMetadataForPost({ client: vlozi, slug: params.slug });
}This returns a complete Next.js Metadata object with:
titlefrompost.seoTitle(fallback:post.title)descriptionfrompost.seoDescription(fallback:post.excerpt)openGraph— typearticle, title, description, image (with width/height), publishedTime, tags as keywordstwittercard metadata mirroring OpenGraph
Options
generateMetadataForPost({
client, // required — your VloziClient
slug, // required — the post slug
notFoundMetadata?: object, // returned when the post doesn't exist; default {}
})Returns notFoundMetadata (empty by default) when the post is not found — Next.js handles 404 gracefully.
To add fields of your own, spread the result:
const base = await generateMetadataForPost({ client: vlozi, slug: params.slug });
return { ...base, alternates: { canonical: `https://mysite.com/blog/${params.slug}` } };Full page example
// app/blog/[slug]/page.tsx
import { generateStaticParamsForPosts, generateMetadataForPost } from "@vlozi/blog/next";
import { BlogContent } from "@vlozi/blog/react";
import { vlozi } from "@/lib/vlozi";
import { notFound } from "next/navigation";
import Image from "next/image";
export async function generateStaticParams() {
return generateStaticParamsForPosts({ client: vlozi });
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
return generateMetadataForPost({ client: vlozi, slug: params.slug });
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await vlozi.blog.get(params.slug).catch(() => null);
if (!post) notFound();
return (
<article className="max-w-3xl mx-auto py-12 px-4">
{post.featuredImageUrl && (
<Image
src={post.featuredImageUrl}
alt={post.title}
width={1200}
height={630}
className="rounded-lg mb-8 w-full object-cover"
priority
/>
)}
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<div className="flex items-center gap-4 text-sm text-muted-foreground mb-8">
<time dateTime={post.publishedAt}>
{new Date(post.publishedAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
{post.category && (
<span className="bg-primary/10 text-primary px-2 py-0.5 rounded-full">
{post.category.name}
</span>
)}
</div>
{post.content && <BlogContent html={post.content} />}
</article>
);
}Blog list page (server-rendered)
// app/blog/page.tsx
import { ServerBlogList } from "@vlozi/blog/server";
import { vlozi } from "@/lib/vlozi";
export default function BlogPage() {
return (
<div className="container py-10">
<h1 className="text-4xl font-bold mb-8">Blog</h1>
<ServerBlogList client={vlozi} limit={12} />
</div>
);
}ServerBlogList is an async React Server Component — it fetches data server-side with zero client JS. For interactive features (search, filter, infinite scroll), use the client-side BlogList with a VloziProvider instead.
How it compares to the client-side approach
| Static generation | Client-side (BlogList) |
|
|---|---|---|
| SEO | Best — HTML in source | Good — requires crawling JS |
| TTFB | Instant (CDN-cached) | Fast (API call on mount) |
| Build time | Scales with post count | None |
| Dynamic filtering | Needs ISR + API | Real-time |
| Recommended for | Post detail pages | Blog list with search/filter |
Posts published after the build
generateStaticParams decides which pages exist. A post published after
next build has no page — and on a fully static export (output: "export")
there is no dynamicParams fallback to render it on demand, so its URL 404s
until the site is rebuilt.
This is worth planning for, because it's the one thing client-side freshness can't fix:
| Your setup | Existing posts change | New posts appear |
|---|---|---|
| Static export, nothing configured | ❌ on next rebuild | ❌ on next rebuild |
Static export + live refresh |
✅ within ~30s | ❌ needs a rebuild |
| Static export + deploy hook | ✅ on rebuild (1–3 min) | ✅ on rebuild |
| ISR (Vercel/Node) + webhook | ✅ seconds | ✅ seconds |
Configure both in the Vlozi Dashboard under Blog → Settings → Site updates. See ISR → for the webhook, and Live content → for browser-side refresh.