Next.js

Static generation

Generate static paths and metadata for every post at build time with generateStaticParamsForPosts.

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/blog

Set 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:

  • title from post.seoTitle (fallback: post.title)
  • description from post.seoDescription (fallback: post.excerpt)
  • openGraph — type article, title, description, image (with width/height), publishedTime, tags as keywords
  • twitter card 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.

Blog · Next.jsEdit on GitHub