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(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, {
  batchSize?: number,  // posts per page request; default 100
  maxPosts?: number,   // hard cap; default unlimited
})

NOTE

For blogs with more than ~1,000 posts, consider setting maxPosts to cap 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(vlozi, params.slug, {
    baseUrl: process.env.NEXT_PUBLIC_SITE_URL,  // e.g. "https://mysite.com"
  });
}

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, slug, {
  baseUrl?: string,       // canonical URL base for OpenGraph; default: ""
  imageWidth?: number,    // default: 1200
  imageHeight?: number,   // default: 630
})

Returns {} (empty metadata) when the post is not found — Next.js handles 404 gracefully.

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(vlozi);
}
 
export async function generateMetadata({ params }: { params: { slug: string } }) {
  return generateMetadataForPost(vlozi, params.slug, {
    baseUrl: process.env.NEXT_PUBLIC_SITE_URL,
  });
}
 
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
Blog · Next.jsEdit on GitHub