UnfoldCMS + Next.js: Headless Integration Guide
Fetch content, render it, and rebuild on publish — with real code.
Most "headless CMS + Next.js" tutorials skip the part that actually bites you: what happens when an editor hits publish and the live site doesn't update. This page walks the full UnfoldCMS + Next.js wiring — fetching content from the REST API, rendering it, and revalidating pages automatically with a signed webhook. Real code, honest setup time (about 30 minutes), and the gotchas nobody mentions. See also: the best CMS for Next.js and the Astro integration guide.
TL;DR: UnfoldCMS runs on your own PHP host. Your Next.js app fetches published content from /api/v1/posts (public, no auth) at build time or with ISR. When an editor publishes, UnfoldCMS fires an HMAC-signed webhook at a Next.js route that calls revalidatePath() — so the page rebuilds within seconds, not on the next deploy. No GraphQL, no SDK to install, just fetch().
How Do UnfoldCMS and Next.js Fit Together?
UnfoldCMS is the content source. Next.js is the frontend. They talk over a plain REST API. UnfoldCMS stays on your server (it's a Laravel app — PHP and MySQL); Next.js can live anywhere — Vercel, Cloudflare Pages, your own box.
The flow has three moving parts:
- Read — Next.js calls
GET /api/v1/postsandGET /api/v1/posts/{slug}to pull published content. - Render — You map the JSON into your components. Server Components fetch directly; no client-side keys needed for public content.
- Revalidate — On publish, UnfoldCMS POSTs a signed webhook to your app, which revalidates the affected page.
That third step is the one that separates a real integration from a demo. Without it, your statically-built pages stay stale until the next full deploy.
What You Need Before Starting
- A running UnfoldCMS install with at least one published post (any tier — the public read API ships in Core).
- A Next.js 14+ app using the App Router.
- The base URL of your CMS, e.g.
https://cms.yoursite.com. - About 30 minutes. The fetch layer takes 10; the webhook revalidation takes 20 if it's your first time.
You do not need an API key to read published content. The public endpoints are open (rate-limited to 60 requests/minute). You only need a token for writes, which a frontend never does.
Step 1: Fetch Content From the API
Start with a small typed client. UnfoldCMS wraps every response in a { success, data } envelope, so unwrap data once and forget about it.
// lib/cms.ts
const CMS_URL = process.env.CMS_URL!; // https://cms.yoursite.com
async function cms<T>(path: string): Promise<T> {
const res = await fetch(`${CMS_URL}/api/v1${path}`, {
next: { tags: ['cms'] }, // ISR: cache, revalidate on webhook (Step 3)
});
if (!res.ok) throw new Error(`CMS ${path} returned ${res.status}`);
const json = await res.json();
return json.data as T;
}
export const getPosts = () => cms<{ data: Post[] }>('/posts');
export const getPost = (slug: string) => cms<{ post: Post }>(`/posts/${slug}`);
Then render it in a Server Component. No 'use client', no data-fetching library, no exposed secret:
// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/cms';
export default async function PostPage({ params }: { params: { slug: string } }) {
const { post } = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.body }} />
</article>
);
}
The body comes back as sanitized HTML, so dangerouslySetInnerHTML is the intended path here — UnfoldCMS runs content through a purifier before it's ever served.
Step 2: Pre-Build Every Post With generateStaticParams
To get static pages (fast, cheap, edge-cacheable), tell Next.js which slugs exist at build time:
// app/blog/[slug]/page.tsx
import { getPosts } from '@/lib/cms';
export async function generateStaticParams() {
const { data: posts } = await getPosts();
return posts.map((p) => ({ slug: p.slug }));
}
Now every published post is a static HTML file. The open question — and the reason most tutorials fall short — is what happens when post #41 gets edited after the build. That's Step 3.
Step 3: Auto-Update on Publish With a Signed Webhook
UnfoldCMS ships outgoing HMAC-signed webhooks. When a post is published, it POSTs a signed payload to a URL you register. Your Next.js app verifies the signature and revalidates the affected path. This is the piece that makes the integration production-grade.
First, the receiver. Add an API route that checks the HMAC signature before doing anything:
// app/api/cms-webhook/route.ts
import crypto from 'crypto';
import { revalidatePath } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(req: NextRequest) {
const raw = await req.text();
// UnfoldCMS signs with spatie/laravel-webhook-server — header "Signature",
// value hash_hmac('sha256', rawBody, secret).
const signature = req.headers.get('signature') ?? '';
const expected = crypto
.createHmac('sha256', process.env.CMS_WEBHOOK_SECRET!)
.update(raw)
.digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return new Response('Bad signature', { status: 401 });
// Payload: { event, occurred_at, data: { id, title, slug, url, posted_at } }
const { event, data } = JSON.parse(raw);
if (data?.slug) revalidatePath(`/blog/${data.slug}`);
revalidatePath('/blog'); // the index, in case the list changed
return Response.json({ revalidated: true, event });
}
Then register it in UnfoldCMS. In the admin, open the webhooks settings, add your endpoint URL (https://yoursite.com/api/cms-webhook), subscribe to the post.published event, and copy the generated signing secret into your Next.js env as CMS_WEBHOOK_SECRET. The admin has a "Send test" button — use it before trusting the wiring.
Now the loop is closed: editor publishes, UnfoldCMS signs and POSTs, your route verifies and revalidates, and the live page reflects the change in seconds with no deploy.
One honest caveat:
revalidatePathneeds your app running as a server (Node or edge runtime), not a pure static export. If yououtput: 'export'to flat files, you'll trigger a full rebuild instead — covered in the Vercel deploy guide.
Build-Time vs ISR vs On-Demand: Which Should You Use?
Three ways to keep Next.js content fresh against UnfoldCMS. Pick by how often editors publish and how much staleness you can tolerate.
| Strategy | Freshness | Cost | Best for |
|---|---|---|---|
| Build-time only | Stale until next deploy | Cheapest | Docs, rarely-edited sites |
| ISR (time-based) | Up to N seconds stale | Low | Blogs with steady cadence |
| On-demand (webhook) | Seconds after publish | Low | Most content sites — recommended |
On-demand revalidation via the signed webhook is the sweet spot: pages stay static and fast, but update almost instantly when something actually changes.
Frequently Asked Questions
Does UnfoldCMS have a GraphQL API for Next.js?
No. UnfoldCMS is REST-only. You query endpoints like /api/v1/posts with plain fetch(). For most Next.js content sites this is simpler than GraphQL — no schema layer, no extra client, and Server Components fetch directly.
Is there an official npm package like @unfoldcms/next?
No SDK exists today. You talk to the API with the native fetch() built into Next.js. The small typed client shown above is all the "SDK" you need — about 15 lines.
Do I need an API key to read posts?
No. Published posts, pages, categories, search, and menus are public read endpoints with no auth, rate-limited to 60 requests per minute. You only need a Sanctum token for write operations, which a frontend never performs.
Can I host UnfoldCMS itself on Vercel?
No — UnfoldCMS is a PHP/Laravel app and Vercel runs JavaScript. Host the CMS on any PHP-capable server, then deploy your Next.js frontend to Vercel and point it at the CMS API. See the Vercel deployment guide.
Can Next.js preview unpublished drafts?
The public API serves published content only — /api/v1/posts/{slug} returns 404 for drafts and future-scheduled posts. For draft preview you'd authenticate with a Sanctum admin token against the admin endpoints and gate it behind Next.js Draft Mode.
Where to Go From Here
You now have content flowing from UnfoldCMS into Next.js, rendering statically, and updating on publish. If you're choosing a CMS rather than wiring one you already picked, the CMS for Next.js comparison weighs UnfoldCMS against Sanity, Payload, and Contentful honestly. To see the full endpoint list, read the API docs. And if you want the same setup on a different framework, the Astro guide follows the identical three-step pattern.
UnfoldCMS is a one-time license, self-hosted, with the full public API in every tier — see pricing. It won't fit every team (you do run your own server), but for developers who want to own their stack and skip per-seat SaaS bills, it's a clean fit.
Related: CMS for Next.js · Deploy on Vercel · Astro integration