In the first post in this two-post series, we learned that WordPress does not currently provide a complete server-side registry of Gutenberg blocks, and that as a result, there are two primary, viable options for rendering blocks in headless WordPress projects:
- Render Gutenberg Blocks as HTML
- Use WPGraphQL Gutenberg
The first post discussed #1, and this post will cover approach #2.
Before diving in, be sure to review the pros and cons of this approach listed in the excellent Gutenberg and Decoupled Applications post on the WPGraphQL blog to determine if it’s right for your project.
How WPGraphQL Gutenberg Works
As stated above, WordPress core doesn’t currently provide a complete server-registry of blocks. As a result, is not possible to query WordPress to get a list of all possible blocks and their data, and add that to the REST API / WPGraphQL schema. For headless sites, this means that you can’t fire off a REST API or WPGraphQL query to get all the blocks for a given post and have all the data you need.
That’s where the WPGraphQL Gutenberg plugin comes in. It is an extension for WPGraphQL that works like this:
- When the Gutenberg block editor JavaScript application boots up, WPGraphQL Gutenberg gets the blocks registry and sends it in a network request to the WordPress PHP application.
- When the request is received, the blocks registry is saved to the database.
- Using the saved registry data, WPGraphQL Gutenberg adds the blocks to the WPGraphQL schema. This way, frontend applications are able to query for blocks and get all of their data.
To gain a more in-depth understanding of how it works, check out the project’s documentation.
Next we’ll learn how to work with WPGraphQL Gutenberg using this Next.js app as an example:
https://github.com/kellenmace/wpgraphql-gutenberg-demo
Setup
To benefit from this post, you should be familiar with the basics of local WordPress development, WPGraphQL, React, and Apollo Client.
Here are the steps for getting set up:
WordPress Backend Setup
- Spin up local WordPress site.
- Install and activate both the WPGraphQL and WPGraphQL Gutenberg plugins.
- Create a couple blog posts using Gutenberg blocks to use for testing.
- Click
WPGraphQL Gutenberg Admin
in the WordPress admin sidebar to be sent to the WPGraphQL Gutenberg options page, then click theUpdate
button to update the blocks registry. This will loop through your posts, open the Gutenberg block editor inside of a hidden iframe for each, and save the blocks registry data to the database.
Next.js App Setup
- Clone down the Next.js app repo.
- Create a
.env.local
file inside of the app’s root folder. Open that file in a text editor and paste inNEXT_PUBLIC_WORDPRESS_API_URL=https://gutenbergdemo.local/graphql
, replacinggutenbergdemo.local
with the domain for your local WordPress site. This is the endpoint that Apollo Client will use when it sends requests to your WordPress backend. - Run
npm install
(oryarn
) to install the app’s NPM dependencies. - Run
npm run dev
to get the server running locally. - You should now be able to visit http://localhost:3000/blog in a web browser and see the app’s Blog page.
Render Blocks
Open pages/[...uri].js
in a code editor. Our single blog post pages will be rendered using this file.
const GET_POST = gql`
query getPost($uri: ID!) {
post(id: $uri, idType: URI) {
title
...BlocksField
}
}
${BLOCKS_FIELD}
`;
export async function getStaticProps(context) {
const uri = context.params.uri.join("/");
const response = await client.query({
query: GET_POST,
variables: { uri },
});
const post = response?.data?.post;
if (!post) {
return { notFound: true };
}
return {
props: { post },
revalidate: 120,
};
}
Code language: JavaScript (javascript)
You can see that inside of the getStaticProps()
function, we run the GET_POST
query and pass to it the URI for the current page as a variable. Once the result comes back, we extract the post data and return it to send it through as a prop to the SinglePost
component in this same file.
SinglePost
looks like this:
export default function SinglePost({ post }) {
const { title, blocks } = post;
return (
<Layout>
<article className="blog-post">
<h1>{title}</h1>
<div>
{blocks
? blocks.map((block, index) => <Block block={block} key={index} />)
: null}
</div>
</article>
</Layout>
);
}
Code language: JavaScript (javascript)
You can see that if we have blocks data to render, we map over the blocks and render a Block
component for each one.
Now open /components/Block.js
so we can take review the Block
component. It looks something like this:
export default function Block({ block }) {
const { attributes, name, innerBlocks } = block;
switch (name) {
case "core/heading":
return <HeadingBlock {...attributes} />;
case "core/paragraph":
return <ParagraphBlock {...attributes} />;
// TODO: Account for all other block types here.
default:
return null;
}
}
Code language: JavaScript (javascript)
This component is little more that a switch()
statement. It takes in the block
prop, determines which block should be rendered based on its name, then renders the corresponding block component.
In your project, you would need to account for all possible types of blocks at this point. Be sure to accommodate nested blocks (such as Column blocks) that need the innerBlocks
data passed to them as a prop, in addition to attributes
. Check out WebDevStudios’ Next.js WordPress Starter to see an example of how to do this.
If you have a 1:1 mapping of blocks to components without the need to wrap any of them in div
s/pass additional props/etc., you could even use this alternative approach which uses a JS object to map each block with its corresponding component.
At the top of the Block.js
file is this GraphQL fragment, which represents the data to be queried for all blocks:
export const BLOCKS_FIELD = gql`
fragment BlocksField on Post {
blocks {
name
... on CoreHeadingBlock {
attributes {
... on CoreHeadingBlockAttributes {
...HeadingBlockAttributes
}
}
}
... on CoreParagraphBlock {
attributes {
... on CoreParagraphBlockAttributes {
...ParagraphBlockAttributes
}
}
}
}
}
${HEADING_BLOCK_ATTRIBUTES}
${PARAGRAPH_BLOCK_ATTRIBUTES}
`;
Code language: JavaScript (javascript)
Notice that the fragments for individual block types are also interpolated into this template literal. This fragment is used in the [...url].js
file we saw earlier with the ...BlocksField
syntax.
Now let’s take a look at an individual block component. Open up components/blocks/ParagraphBlock.js
.
export const PARAGRAPH_BLOCK_ATTRIBUTES = gql`
fragment ParagraphBlockAttributes on CoreParagraphBlockAttributes {
align
anchor
backgroundColor
className
content
dropCap
style
textColor
}
`;
function getClassName(align) {
if (align === "center" || align === "right") {
return `text-${align}`;
}
return "text-left";
}
export default function ParagraphBlock({
align,
anchor,
backgroundColor,
className,
content,
dropCap,
style,
textColor,
}) {
return <p className={getClassName(align)}>{parseHtml(content)}</p>;
}
Code language: JavaScript (javascript)
At the top of this file is a GraphQL fragment in which we specify all the attributes we want included in our queries for Paragraph blocks.
Now take a look at the ParagraphBlock
component. You can see that we’re able to restructure the props passed in to pull out all of those same attributes.
Having the fragment and the destructuring assignment colocated in the same file like this makes it easy to keep the two in sync; you can see all the data being requested, and all the data being received all in one shot.
Disregard the parseHtml()
function the content
is passed through for the time being; we’ll discuss that in the next section.
To see examples of many individual block components beyond the ones in this app’s repo, check out these from WebDevStudios’ Next.js WordPress Starter.
Fix Internal Links
Custom Server-side Block Parser
Using this method, you’ll notice that internal links inside of Gutenberg blocks still point to the domain where your WordPress backend lives. You can fix that by using this Headless Block Parser plugin. Follow the steps in the readme to make use of it in your project.
With that plugin in place, an internal link pointing to https://my-wp-backend.local/blog/hello-world
in the post content will be rewritten to http://localhost:3000/blog/hello-world
, for example. So make sure that your frontend app’s routing is set up property to accommodate that.
An alternative approach you could take here would be to remove the domain, turning the links into relative URLs, such as /blog/hello-world
. If you go that route, just be careful to account for all possible URL permutations– those that contain anchor links or query string parameters, those that point to the homepage (/
), and so on.
Convert Anchor tags to Link Components
Above, I showed how to filter the blocks content to replace the domain of internal links with the domain of your decoupled JavaScript app. For single-page app (SPA) frameworks however, that isn’t quite enough. Although the internal links now point to the correct URL, they’re still just plain ol’ anchor tags (<a>
). That means that when the user clicks one, a full page reload will be triggered rather than a route change using the SPA framework’s router. Let’s see how we can fix that and turn them into Link
components instead using a JavaScript-based HTML parser.
If you open up the package.json file
, you’ll see that the html-react-parser library has been installed.
Head over to /lib/parser.js
now to see how it’s used.
import parse, { domToReact } from "html-react-parser";
import Link from "next/link";
export default function parseHtml(html) {
const options = {
replace: ({ name, attribs, children }) => {
// Convert internal links to Next.js Link components.
const isInternalLink =
name === "a" && attribs["data-internal-link"] === "true";
if (isInternalLink) {
return (
<Link href={attribs.href}>
<a {...attribs}>{domToReact(children, options)}</a>
</Link>
);
}
},
};
return parse(html, options);
}
Code language: JavaScript (javascript)
The parse()
function that html-react-parser
provides parses the string of HTML into nodes. By passing in the options
object with a replace()
callback function inside, we tell the parser that when it encounters an anchor tag with a data-internal-link
data attribute of true
(an internal link), replace it with a Next.js Link
component.
Now we can then make use of this new parseHtml()
function we created in our individual block components, like this:
import parseHtml from "../../lib/parser";
export default function ParagraphBlock({
align,
anchor,
backgroundColor,
className,
content,
dropCap,
style,
textColor,
}) {
return <p className={getClassName(align)}>{parseHtml(content)}</p>;
}
Code language: JavaScript (javascript)
As a result of this work, our site visitors will be able to click on an internal link inside of a block’s content and experience an instantaneous route change via Next.js’ router, with no more full page reload.
Other Uses
The example above that shows converting internal anchor tag links to Link
components is one use-case. You could use a parsing library to render components for other nodes inside of your blocks’ content as well though, if needed.
Style Blocks
In the first “Render Blocks as HTML” post in this series, we saw how it’s possible to import several stylesheets from WordPress core that provide base styles for the blocks HTML.
Since this approach focuses on using WPGraphQL Gutenberg to query for JSON data and render all the block markup/JSX ourselves, however, you would also need to write custom styles that target each type of block.
Trade-off: Control vs. Ease of Implementation
As you can likely see, this approach gives developers a ton of control. You can query to get JSON data for your blocks all the way down to their individual attributes, then render and style them however you want. Implementing this approach requires a significant amount of work, however, so be prepared for that.
In the first “Render Blocks as HTML” post in this series, I described how it’s possible to use an HTML parsing library to replace a few HTML nodes with components, such as the Link
component that your JS framework provides. Working with WPGraphQL Gutenberg is quite the opposite experience; you must render components for every type of block yourself. If you need that level of control for your project, the additional effort could be worth it, though.
As I mentioned at the top of this post, be sure to also review the pros and cons of this approach listed in the excellent Gutenberg and Decoupled Applications post on the WPGraphQL blog to determine if it’s right for your project.
Wrapping Up
I hope this post gave you a good sense of what querying for and rendering Gutenberg blocks using WPGraphQL Gutenberg looks like in practice. I also hope it’s a helpful piece to reference reference if you choose to implement this approach in your own headless WordPress projects.
Do you have any questions about this method of rendering Gutenberg blocks content? Please reach out to let us know!