Render BlockNote Blocks in Next.js
Convert BlockNote blocks from the Article API into HTML using @blocknote/server-util.
Request your article with format=block so the API returns raw BlockNote blocks in data.article.content.
curl -s \
-H "x-api-key: <YOUR_API_KEY>" \
"https://www.agent-engineering.dev/api/v1/articles/testing?format=block"The content field is JSON:
{
"success": true,
"message": "OK",
"data": {
"article": {
"id": "…",
"title": "…",
"content": [
{
"id": "…",
"type": "paragraph",
"props": {},
"content": [{ "type": "text", "text": "Hello BlockNote" }],
"children": []
}
]
}
}
}Add BlockNote packages to serverExternalPackages so Next.js treats them as server externals.
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
serverExternalPackages: ["@blocknote/server-util", "@blocknote/react"],
};
export default nextConfig;This project already includes it in next.config.ts.
This is the same pattern used by the site’s article renderer in article pages.
import { Block } from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { ServerBlockNoteEditor } from "@blocknote/server-util";
import "@blocknote/shadcn/style.css";
type Props = {
blocks: Block[];
};
export async function BlockNoteHtml({ blocks }: Props) {
const editor = ServerBlockNoteEditor.create();
const html = await editor.blocksToFullHTML(blocks);
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}If your blocks come from JSON (like Prisma JSON or the API response), cast them to Block[] after you validate the caller is trusted.
import { Block } from "@blocknote/core";
import { BlockNoteHtml } from "./block-note-html";
type ArticleApiResponse = {
success: boolean;
message: string;
data?: {
article: {
title: string;
content: unknown;
};
};
};
export default async function Page() {
const res = await fetch("https://www.agent-engineering.dev/api/v1/articles/testing?format=block", {
headers: {
"x-api-key": process.env.AGENT_ENGINEERING_API_KEY!,
},
cache: "no-store",
});
if (!res.ok) throw new Error(await res.text());
const body = (await res.json()) as ArticleApiResponse;
if (!body.success || !body.data) throw new Error(body.message);
const blocks = body.data.article.content as Block[];
return (
<main>
<h1>{body.data.article.title}</h1>
<BlockNoteHtml blocks={blocks} />
</main>
);
}For client-side rendering, prefer converting to HTML on the server and sending HTML down, unless you explicitly need a client editor.
If your BlockNote content includes code blocks, the site uses Highlight.js to apply syntax highlighting.
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css"
/>
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"
strategy="afterInteractive"
/>
<Script
id="highlight-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `(function () {
function tryHighlight(attempt) {
if (window.hljs && typeof window.hljs.highlightAll === "function") {
window.hljs.highlightAll();
return;
}
if (attempt >= 60) return;
setTimeout(function () {
tryHighlight(attempt + 1);
}, 50);
}
if (document.readyState === "complete") {
tryHighlight(0);
} else {
window.addEventListener("load", function () {
tryHighlight(0);
});
}
})();`,
}}
/>