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"}'Let Vlozi call you (recommended)
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).