Render WordPress pages block by block in Next.js
Fetch prepared WordPress page JSON with a blocks field, map every block to a frontend component, and keep WordPress as the editing interface.
The problem
Headless WordPress often starts with rendered HTML. That works quickly, but it makes frontend control harder when the page is built from Gutenberg blocks.
Without block-level JSON, a Next.js app has less control over layout, components, media handling, and the way page sections are cached.
Rendered HTML treated as one blob
Harder component mapping in Next.js
Less control over media per section
Repeated reads from WordPress REST
Cache rules applied to entire pages only
Solution
Smooth API Accelerator can prepare WordPress REST responses as CDN-served JSON. When your response includes a blocks array, the frontend can render each block with a matching React component instead of treating the whole page as raw HTML.
1
Prepare page JSON with blocks
Expose a WordPress page payload that keeps normal page fields and adds block data. The exact shape can follow your theme or plugin, but the frontend should receive a stable block name, attributes, optional HTML, and nested blocks when needed.
{
"id": 42,
"slug": "about",
"title": "About us",
"blocks": [
{
"name": "core/heading",
"attrs": {"level": 1},
"innerHTML": "About us"
},
{
"name": "core/paragraph",
"attrs": {},
"innerHTML": "We build fast publishing workflows."
},
{
"name": "core/image",
"attrs": {"url": "https://cdn.smoothcdn.com/site/pages/about/team.jpg"}
}
]
}2
Fetch the prepared page in Next.js
The Next.js route can read from the Smooth CDN JSON URL instead of hitting WordPress on every request. Revalidation stays close to the frontend and WordPress remains the place where editors manage content.
import {BlockRenderer} from "@/app/components/BlockRenderer";
async function getPage(slug: string) {
const response = await fetch(
`https://cdn.smoothcdn.com/user/my-nextjs-cms/wp-json/pages/${slug}.json`,
{next: {revalidate: 300}}
);
if (!response.ok) {
return null;
}
return response.json();
}
export default async function Page({params}: {params: Promise<{slug: string}>}) {
const {slug} = await params;
const page = await getPage(slug);
if (!page) {
return null;
}
return (
<main>
{page.blocks.map((block: any, index: number) => (
<BlockRenderer key={index} block={block}/>
))}
</main>
);
}3
Render every block with a component
Keep a small map between WordPress block names and React components. Unknown blocks can fall back to sanitized HTML or be skipped depending on your content policy.
const blocks: Record<string, (props: any) => React.ReactNode> = {
"core/heading": ({block}) => {
const Tag = `h${block.attrs?.level || 2}` as keyof JSX.IntrinsicElements;
return <Tag>{block.innerHTML}</Tag>;
},
"core/paragraph": ({block}) => <p>{block.innerHTML}</p>,
"core/image": ({block}) => (
<img src={block.attrs.url} alt={block.attrs.alt || ""} loading="lazy"/>
),
};
export function BlockRenderer({block}: {block: any}) {
const Component = blocks[block.name];
if (!Component) {
return null;
}
return <Component block={block}/>;
}What You get
A headless WordPress flow where editors keep using blocks and developers keep control over React rendering, caching, and asset delivery.
WordPress remains the source of content
Next.js renders block-aware components
Prepared JSON can be served from Smooth CDN
Better control over media and page sections
Keep editing in WordPress. Render with Next.js.
Move repeated WordPress API reads to prepared JSON and render blocks with frontend components.