This project uses Freewrite CMS to manage content like changelogs, blog posts, and documentation pages. The integration follows the shadcn philosophy — two copy-paste files you own and can customize freely.
| File | Purpose |
|---|---|
lib/freewritecms.ts |
Types + fetchFreewriteCMS() client |
components/freewritecms-block-renderer.tsx |
Block content → React component renderer |
- Go to Freewrite CMS and sign in
- Navigate to your project settings
- Generate or copy your API key
Add the following to your .env.local:
FREEWRITE_API_KEY=your-freewrite-api-keyThe two files (lib/freewritecms.ts and components/freewritecms-block-renderer.tsx) are already in the project. No packages to install.
Use fetchFreewriteCMS() in any server component or API route:
import { fetchFreewriteCMS, type Block } from "@/lib/freewritecms";
// Define the shape of your response
interface ChangelogNode {
id: string;
title: string;
content: Block[];
publishedAt: string;
}
interface ChangelogsData {
changelogs: {
edges: Array<{ node: ChangelogNode }>;
};
}
// Fetch with full type safety
const { data } = await fetchFreewriteCMS<ChangelogsData>({
query: `
query GetChangelogs {
changelogs {
edges {
node {
id
title
content
publishedAt
}
}
}
}
`,
revalidate: 60, // ISR: revalidate every 60 seconds
});import { FreeWriteCmsBlockRenderer } from "@/components/freewritecms-block-renderer";
import type { Block } from "@/lib/freewritecms";
<FreeWriteCmsBlockRenderer blocks={content as Block[]} />
// With custom wrapper class
<FreeWriteCmsBlockRenderer blocks={content as Block[]} className="prose" />The renderer handles the following block types out of the box:
| Block Type | Renders As |
|---|---|
paragraph |
<p> |
heading |
<h1> – <h6> |
image |
<figure> with <img> and optional <figcaption> |
code |
<pre><code> |
list |
<ol> or <ul> |
quote |
<blockquote> with optional author |
divider |
<hr> |
fetchFreewriteCMS() accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
query |
string |
required | GraphQL query string |
variables |
Record<string, unknown> |
undefined |
GraphQL variables |
revalidate |
number | false |
60 |
Next.js ISR revalidation interval in seconds |
apiKey |
string |
process.env.FREEWRITE_API_KEY |
Override API key |
apiUrl |
string |
https://www.freewritecms.com/api/graphql |
Override API URL |
Since you own the code, customize freely:
- Swap
<img>for Next.js<Image>— edit theimagecase inBlockDisplayinside the renderer - Add syntax highlighting — integrate a library like
shikiorprismin thecodecase - Custom block types — add new cases to the
switchstatement inBlockDisplay - Styling — all classes use shadcn CSS variables (
text-muted-foreground,bg-muted, etc.), so they automatically follow your theme
See app/(public)/changelog/page.tsx for a complete working example.
To use this integration in another project:
- Copy
lib/freewritecms.tsinto your project'slib/directory - Copy
components/freewritecms-block-renderer.tsxinto your project'scomponents/directory - Ensure your project has the
cn()utility (standard in any shadcn project —lib/utils.ts) - Set the
FREEWRITE_API_KEYenvironment variable