@forumulate/next
v0.2.0
Published
Forumulate for Next.js: cached server-component content fetchers (tags + revalidate) and the comments, search, changelog and feed client components.
Maintainers
Readme
@forumulate/next
Forumulate for the Next.js App Router: content fetchers for Server Components, cached in Next's Data Cache with revalidate and per-item cache tags, plus the comments, search, changelog and feed client components.
Install
npm install @forumulate/nextFetch content in a Server Component
// lib/forumulate.ts
import { createForumulate } from '@forumulate/next';
export const blog = createForumulate({ org: 'acme', space: 'blog', revalidate: 300 });
// app/blog/[slug]/page.tsx
import { blog } from '@/lib/forumulate';
import { ForumulateComments } from '@forumulate/next';
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await blog.getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.excerpt}</p>
<ForumulateComments org="acme" space="community" theme="auto" />
</article>
);
}Fetchers: getPage, getPageMarkdown, listPages, getPost, listPosts, getTopic, listTopics, listReleases, search and getSpace, either on a bound createForumulate(...) or standalone as getPost({ org, space }, slug). The raw client is blog.client.
On-demand revalidation
Every request is tagged. Drop exactly what changed, for example from a webhook route handler:
import { revalidateTag } from 'next/cache';
import { forumulateTags } from '@forumulate/next';
revalidateTag(forumulateTags.post('acme', 'blog', slug));
revalidateTag(forumulateTags.space('acme', 'blog')); // everything in the spaceA content library (headless CMS)
@forumulate/next/content (server only) reads a content library with Next's Data Cache, revalidates
on publish, and serves drafts in draft mode.
// lib/cms.ts
import { createContent } from '@forumulate/next/content';
export const cms = createContent({ library: 'lib_…', key: process.env.FORUMULATE_KEY!, revalidate: false });
// app/[[...path]]/page.tsx
const hit = await cms.resolveRoute(`/${(await params).path?.join('/') ?? ''}`);
// app/api/revalidate/route.ts — the webhook endpoint (Settings → Delivery → Webhooks)
import { createRevalidateHandler } from '@forumulate/next/content';
export const POST = createRevalidateHandler({ secret: process.env.FORUMULATE_WEBHOOK_SECRET! });
// app/api/preview/route.ts — the studio's Preview button posts here
import { createPreviewRoute } from '@forumulate/next/content';
export const POST = createPreviewRoute({ library: 'lib_…' });
// app/api/exit-preview/route.ts
import { createExitPreviewRoute } from '@forumulate/next/content';
export const POST = createExitPreviewRoute();Every read is tagged with the API's cache tags (fm:type:…, fm:entry:…, fm:global:…,
fm:route:…), and the revalidate handler verifies the webhook signature (HMAC v1, five-minute
window) before calling revalidateTag for each tag the publish names. The preview route exchanges
the studio's one-time token server-side, stores the credential in an HttpOnly; Secure;
SameSite=None; Partitioned cookie, enables draftMode(), and redirects to a relative path
only. In draft mode the same reads return drafts and skip the Data Cache.
Client components and hooks
The components (ForumulateComments, ForumulateWidget, ForumulateSearch, ForumulateChangelog, ForumulateFeed and ForumulateProvider) are exported from both entries. The hooks (usePosts, useSearch, ...) are on @forumulate/next/client.
License
MIT
