Next.js

ISR and cache revalidation

Keep statically-generated post pages fresh with Next.js Incremental Static Regeneration.

Statically generated pages become stale when you publish or edit a post. Next.js Incremental Static Regeneration (ISR) lets you regenerate individual pages on-demand — without a full rebuild.

ISR tags

The SDK ships two ISR tag helpers:

import {
  VLOZI_BLOG_TAG,   // "vlozi-blog" — tags the entire blog (list + every post)
  vloziPostTag,     // (slug) => "vlozi-post:<slug>" — tags a single post
} from "@vlozi/blog/next";

Wire them into your fetch() calls (or pass them to the VloziClient constructor) so Next.js knows which cache entries to invalidate when you revalidate.

Option A — Set tags on the VloziClient

This is the simplest approach. Every fetch the client makes inherits the tags automatically:

// lib/vlozi.ts
import { VloziClient } from "@vlozi/blog";
import { VLOZI_BLOG_TAG, vloziPostTag } from "@vlozi/blog/next";
 
// Module-scope client for server-side use
export const vlozi = new VloziClient({
  apiKey: process.env.VLOZI_API_KEY ?? "",
  baseUrl: process.env.VLOZI_BASE_URL ?? "",
  nextTags: [VLOZI_BLOG_TAG],  // tag every fetch this client makes
});

For post detail pages, you can add a per-post tag at fetch time:

// app/blog/[slug]/page.tsx
const post = await vlozi.blog.get(params.slug, {
  nextTags: [vloziPostTag(params.slug)],  // in addition to VLOZI_BLOG_TAG
});

Option B — Set tags per-fetch in RSC

import { unstable_cache } from "next/cache";
import { VLOZI_BLOG_TAG, vloziPostTag } from "@vlozi/blog/next";
 
const getPost = unstable_cache(
  (slug: string) => vlozi.blog.get(slug),
  ["blog-post"],
  { tags: [VLOZI_BLOG_TAG, vloziPostTag("__slug__")] }
);

Revalidating

revalidateVloziBlog

Programmatically trigger revalidation from anywhere (API route, server action, webhook handler):

import { revalidateVloziBlog } from "@vlozi/blog/next";
 
// Revalidate the collection — list, category, tag and archive pages
await revalidateVloziBlog();
 
// Also revalidate one post's own page
await revalidateVloziBlog({ slug: "my-post-slug" });
 
// Add your own tags alongside the standard ones
await revalidateVloziBlog({ slug: "my-post-slug", tags: ["homepage"] });

Under the hood, this calls Next.js revalidateTag() using a dynamic import — the SDK never bundles next/cache into client code.

From a Route Handler (webhook)

Create a route handler that Vlozi (or your own code) can call after publishing:

// app/api/revalidate-blog/route.ts
import { NextResponse } from "next/server";
import { revalidateVloziBlog } from "@vlozi/blog/next";
 
export async function POST(request: Request) {
  const { slug } = await request.json().catch(() => ({}));
 
  if (slug) {
    await revalidateVloziBlog({ slug });
  } else {
    await revalidateVloziBlog();
  }
 
  return NextResponse.json({ revalidated: true });
}

Then after publishing a post in the Vlozi Dashboard, call this endpoint:

curl -X POST https://yoursite.com/api/revalidate-blog \
  -H "Content-Type: application/json" \
  -d '{"slug": "my-new-post"}'

You don't have to trigger this yourself. Mount the handler the SDK ships and Vlozi will call it whenever content changes:

// app/api/vlozi/revalidate/route.ts
import { handleVloziWebhook } from "@vlozi/blog/next";
 
export const POST = (request: Request) =>
  handleVloziWebhook(request, { secret: process.env.VLOZI_WEBHOOK_SECRET! });

Then in the Vlozi Dashboard open Blog → Settings → Site updates, choose Instant revalidate, paste your route's URL, and copy the signing secret into VLOZI_WEBHOOK_SECRET. Press Send test to confirm it works before relying on it.

Deliveries are signed with HMAC-SHA256 over `${timestamp}.${rawBody}` and sent as X-Vlozi-Signature: sha256=<hex>. handleVloziWebhook verifies that and rejects anything older than five minutes, so a captured request can't be replayed later.

If you need to do your own work after verification, compose the pieces:

import { verifyVloziSignature, revalidateVloziBlog } from "@vlozi/blog/next";
 
export async function POST(request: Request) {
  const raw = await request.text();               // must be the RAW body
  const result = await verifyVloziSignature(request, raw, {
    secret: process.env.VLOZI_WEBHOOK_SECRET!,
  });
  if (!result.ok) return Response.json({ error: result.reason }, { status: result.status });
 
  const { slug } = JSON.parse(raw);
  await revalidateVloziBlog(slug ? { slug } : {});
  return Response.json({ revalidated: true });
}

IMPORTANT

Read the body with request.text(), not request.json(). The signature covers the exact bytes we sent — re-serializing a parsed object changes key order and whitespace, and verification will never match.

NOTE

On a fully static export (output: "export") there is no server to re-render, so on-demand revalidation can't help. Use the Rebuild site mode instead, which triggers your host's deploy hook.

Setting revalidate on pages

If you use time-based revalidation instead of (or in addition to) on-demand revalidation, export revalidate from your page:

// app/blog/[slug]/page.tsx
 
// Re-generate each post at most once per hour
export const revalidate = 3600;
 
// Or: always serve the cached version but regenerate in background
export const revalidate = "force-cache";

For most blogs, on-demand revalidation is better — pages only rebuild when content actually changes, not on a fixed schedule.

Complete setup example

// lib/vlozi.ts — server-side client with ISR tags
import { VloziClient } from "@vlozi/blog";
import { VLOZI_BLOG_TAG } from "@vlozi/blog/next";
 
export const vlozi = new VloziClient({
  apiKey: process.env.VLOZI_API_KEY ?? "",
  baseUrl: process.env.VLOZI_BASE_URL ?? "",
  nextTags: [VLOZI_BLOG_TAG],
});
// app/blog/[slug]/page.tsx — static + per-post ISR tag
import {
  generateStaticParamsForPosts,
  generateMetadataForPost,
  vloziPostTag,
  VLOZI_BLOG_TAG,
} from "@vlozi/blog/next";
import { VloziClient } from "@vlozi/blog";
import { BlogContent } from "@vlozi/blog/react";
import { vlozi } from "@/lib/vlozi";
import { notFound } from "next/navigation";
 
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 } }) {
  // `nextTags` is set when the client is CONSTRUCTED, not per request — so a
  // page that wants its own per-post tag builds a client carrying both.
  const client = new VloziClient({
    apiKey: process.env.VLOZI_API_KEY ?? "",
    baseUrl: process.env.VLOZI_BASE_URL ?? "",
    nextTags: [VLOZI_BLOG_TAG, vloziPostTag(params.slug)],
  });
 
  const post = await client.blog.get(params.slug).catch(() => null);
 
  if (!post) notFound();
 
  return (
    <article>
      <h1>{post.title}</h1>
      {post.content && <BlogContent html={post.content} />}
    </article>
  );
}
// app/api/vlozi/revalidate/route.ts — let Vlozi call you on every change
import { handleVloziWebhook } from "@vlozi/blog/next";
 
export const POST = (request: Request) =>
  handleVloziWebhook(request, { secret: process.env.VLOZI_WEBHOOK_SECRET! });

What each tag covers

Tag Revalidates
VLOZI_BLOG_TAG ("vlozi-blog") Everything — blog list pages + all post detail pages
vloziPostTag(slug) ("vlozi-post:<slug>") Only the single post detail page for that slug
Both tags on a detail page Revalidates individually (via slug) or globally (via VLOZI_BLOG_TAG)

Use VLOZI_BLOG_TAG alone for simple setups. Add vloziPostTag per-post when you want surgical revalidation (e.g., editing one post without revalidating all post pages).

Blog · Next.jsEdit on GitHub