export const metadata = { title: "Rendering Blocks", };
In headless WordPress, it's possible to take the HTML for an entire page and insert it into the DOM in your decoupled front-end app. On most sites, though, it becomes necessary to map WordPress blocks to React components in your front-end app so you can control how individual blocks are rendered. This document details how to do just that.
Note that this page only covers rendering native WordPress core blocks. Once you have that set up, if you want to go further, you can follow our documentation on how to override WordPress core blocks or work with custom blocks.
If you haven't already, follow the Basic Setup to get Faust.js set up.
1. Set up the WPGraphQL Content Blocks plugin on the WP backend. Head over to the GitHub repo and download the latest version of the wp-graphql-content-blocks plugin from the releases tab.
2. Upload the plugins .zip file to your WordPress site via the plugins page or unzip and copy the file contents into your WordPress wp-content/plugins folder manually.
3. Activate the plugin on the WordPress plugins page.
Once the plugin is installed and activated, head over to the GraphQL IDE. You can find it in the WordPress toolbar.
You should be able to perform queries for the block data. There is a new field added in the Post and Page models called editorBlocks. This represents a list of available blocks for that content type:
If you search in GraphQL IDE documentation explorer tab for the editorBlocks type you will be able to see the available block fields. The most important ones are:
-
renderedHTML: It's the HTML of the block as rendered by the render_block function. -
name: The actual name of the block taken from itsblock.jsonspec. -
__typename: The type of block transformed from thenamefield in camel-case notation. -
apiVersion: The apiVersion of the block taken from itsblock.jsonspec. -
innerBlocks: The inner block list of that block. -
isDynamic: Whether the block is dynamic or not, taken from itsblock.jsonspec. -
clientId,parentClientId: Unique identifiers for the block and the parent of the block. We will explain their usage later.
The plugin iterates over the block.json types as registered within WordPress and creates WPGraphQL types and resolvers. As long as your blocks use the register_block_type function passing a block.json, it will be available in the system without any extra steps.
In WordPress block development, a block.json file is a configuration file that provides metadata and settings for a block. It defines the block’s name, title, icon, category, and other settings related to its appearance and behavior. For more information, see the section on block.json files in the WordPress Developer’s Handbook.
As an example, given the following block.json definition of a block:
// block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"name": "my-plugin/notice",
"icon": "star",
"version": "1.0.3",
"attributes": {
"message": {
"type": "string",
"source": "html",
"selector": ".message"
}
}
}The plugin will create the following WPGraphQL type:
type MyPluginNotice {
attributes: MyPluginNoticeAttributes;
}
type MyPluginNoticeAttributes {
message: String;
}When you request to resolve the message attribute for MyPluginNotice, the plugin will use a resolver that tries to extract the field by sourcing the text element using the selector. As an example, with the following HTML:
<div class="message">Hello World</div>Since the block.json message attribute uses the .message class selector to source the text for that field, this will resolve to:
"Hello World"
Currently, the plugin handles the following attribute types taken from the reference list:
-
boolean
-
number
-
integer
-
string
-
object
-
array
Install the @faustwp/blocks NPM packages in your Next.js app.
npm install @faustwp/blocksIn the root of your Next.js project, set up wp-blocks/index.js with this:
import { CoreBlocks } from "@faustwp/blocks";
export default {
...CoreBlocks,
};To query specific block data you need to define that data in the contentBlock as the appropriate type. Here is an example query to get all the rendered HTML from the editorBlocks field:
query GetBlocksByURI($uri: ID!) {
post(id: $uri, idType: URI) {
title
editorBlocks {
__typename
name
renderedHtml
... on CoreParagraph {
attributes {
className
}
}
}
}
}This WPGraphQL query is designed to retrieve specific data for a single post from WordPress, focusing on block-based content. Here’s what it does:
-
post(id: $uri, idType: URI):- The query retrieves a post based on its URI. The
idType: URIindicates that the post is identified by its unique slug (e.g., "example-page"), passed as a variable ($uri). - This ensures that the content returned corresponds to the post or page associated with that specific URI.
- The query retrieves a post based on its URI. The
-
title:- The
titlefield fetches the title of the queried post. This is the only post-level information returned by the query.
- The
-
editorBlocks:- This field queries the blocks used within the post, such as paragraphs, headings, lists, etc.
- It returns:
__typename: Indicates the block type (e.g.,CoreParagraph,CoreHeading), allowing the frontend to distinguish between different block types.name: Provides the block's name as registered in the WordPress block editor (e.g.,core/paragraph).renderedHtml: Contains the fully rendered HTML for the block, allowing direct rendering of the block content in the frontend.
-
... on CoreParagraph:- This is a fragment specific to
CoreParagraphblocks. It queries additional attributes for paragraph blocks:attributes { className }: Fetches theclassNameattribute if it exists, allowing for custom CSS classes to be applied to paragraph blocks.
- This is a fragment specific to
Now, we can add that query into a Next.js dynamic route file to grab a single post detail page and all its block data. For example:
import { gql, useQuery } from "@apollo/client";
import { useRouter } from "next/router";
const GET_BLOCKS_BY_URI = gql`
query GetBlocksByURI($uri: ID!) {
post(id: $uri, idType: URI) {
title
editorBlocks {
__typename
name
renderedHtml
... on CoreParagraph {
attributes {
className
}
}
}
}
}
`;
export default function SinglePost() {
const router = useRouter();
const { slug } = router.query; // Dynamically get the slug from the URL
const { loading, error, data } = useQuery(GET_BLOCKS_BY_URI, {
variables: { uri: slug }, // Pass the dynamic slug to the query
skip: !slug, // Skip the query if slug is not available yet
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
const { post } = data;
const { title, editorBlocks } = post;
return (
<div className="container mx-auto px-4 py-8">
<h1 className="mb-4 text-3xl font-bold">{title}</h1>
{/* Render the blocks */}
<div className="prose prose-lg prose-invert">
{editorBlocks.map((block, index) => {
return (
<div
key={index}
dangerouslySetInnerHTML={{ __html: block.renderedHtml }}
/>
);
})}
</div>
</div>
);
}This code will render a single post detail page with all the block data coming from WordPress.
Next, you can use the template hierarchy to map block types to React components.
