{"id":282,"date":"2021-08-31T20:36:29","date_gmt":"2021-09-01T02:36:29","guid":{"rendered":"https:\/\/developers.wpengine.com\/blog\/?p=282"},"modified":"2023-04-19T08:53:50","modified_gmt":"2023-04-19T14:53:50","slug":"gutenberg-in-headless-wordpress-wpgraphql-gutenberg","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/","title":{"rendered":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg"},"content":{"rendered":"\n<p>In <a href=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-render-blocks-as-html\/\">the first post<\/a> 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:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><a href=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-render-blocks-as-html\/\">Render Gutenberg Blocks as HTML<\/a><\/li><li>Use WPGraphQL Gutenberg<\/li><\/ol>\n\n\n\n<p>The first post discussed #1, and this post will cover approach #2.<\/p>\n\n\n\n<p>Before diving in, be sure to review the pros and cons of this approach listed in the excellent <a href=\"https:\/\/www.wpgraphql.com\/2021\/03\/09\/gutenberg-and-decoupled-applications\/#gutenberg-blocks-as-html\">Gutenberg and Decoupled Applications<\/a> post on the WPGraphQL blog to determine if it&#8217;s right for your project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How WPGraphQL Gutenberg Works<\/h2>\n\n\n\n<p>As stated above, WordPress core doesn&#8217;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 <a href=\"https:\/\/developer.wordpress.org\/rest-api\/\">REST API<\/a> \/ <a href=\"https:\/\/www.wpgraphql.com\/\">WPGraphQL<\/a> schema. For headless sites, this means that you can&#8217;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.<\/p>\n\n\n\n<p>That&#8217;s where the <a href=\"https:\/\/github.com\/pristas-peter\/wp-graphql-gutenberg\">WPGraphQL Gutenberg<\/a> plugin comes in. It is an extension for <a href=\"https:\/\/www.wpgraphql.com\/\">WPGraphQL<\/a> that works like this:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>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.<\/li><li>When the request is received, the blocks registry is saved to the database.<\/li><li>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.<\/li><\/ul>\n\n\n\n<p>To gain a more in-depth understanding of how it works, check out the project&#8217;s <a href=\"https:\/\/wp-graphql-gutenberg.netlify.app\/\">documentation<\/a>.<\/p>\n\n\n\n<p>Next we&#8217;ll learn how to work with WPGraphQL Gutenberg using this Next.js app as an example:<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/kellenmace\/wpgraphql-gutenberg-demo\">https:\/\/github.com\/kellenmace\/wpgraphql-gutenberg-demo<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setup<\/h2>\n\n\n\n<p>To benefit from this post, you should be familiar with the basics of local&nbsp;<a href=\"https:\/\/wordpress.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">WordPress<\/a>&nbsp;development,&nbsp;<a href=\"https:\/\/www.wpgraphql.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">WPGraphQL<\/a>,&nbsp;<a href=\"https:\/\/reactjs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">React<\/a>, and&nbsp;<a href=\"https:\/\/www.apollographql.com\/docs\/react\/\" target=\"_blank\" rel=\"noreferrer noopener\">Apollo Client<\/a>.<\/p>\n\n\n\n<p>Here are the steps for getting set up:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">WordPress Backend Setup<\/h3>\n\n\n\n<ol class=\"wp-block-list\"><li>Spin up local WordPress site.<\/li><li>Install and activate both the <a href=\"https:\/\/wordpress.org\/plugins\/wp-graphql\/\">WPGraphQL<\/a> and <a href=\"https:\/\/github.com\/pristas-peter\/wp-graphql-gutenberg\">WPGraphQL Gutenberg<\/a> plugins.<\/li><li>Create a couple blog posts using Gutenberg blocks to use for testing.<\/li><li>Click <code>WPGraphQL Gutenberg Admin<\/code> in the WordPress admin sidebar to be sent to the WPGraphQL Gutenberg options page, then click the <code>Update<\/code> 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.<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Next.js App Setup<\/h3>\n\n\n\n<ol class=\"wp-block-list\"><li>Clone down the <a href=\"https:\/\/github.com\/kellenmace\/wpgraphql-gutenberg-demo\">Next.js app repo<\/a>.<\/li><li>Create a&nbsp;<code>.env.local<\/code>&nbsp;file inside of the app\u2019s root folder. Open that file in a text editor and paste in&nbsp;<code>NEXT_PUBLIC_WORDPRESS_API_URL=https:\/\/gutenbergdemo.local\/graphql<\/code>, replacing&nbsp;<code>gutenbergdemo.local<\/code>&nbsp;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.<\/li><li>Run&nbsp;<code>npm install<\/code>&nbsp;(or&nbsp;<code>yarn<\/code>) to install the app\u2019s NPM dependencies.<\/li><li>Run&nbsp;<code>npm run dev<\/code>&nbsp;to get the server running locally.<\/li><li>You should now be able to visit&nbsp;<a href=\"http:\/\/localhost:3000\/blog\/\" target=\"_blank\" rel=\"noreferrer noopener\">http:\/\/localhost:3000\/blog<\/a>&nbsp;in a web browser and see the app&#8217;s Blog page.<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Render Blocks<\/h2>\n\n\n\n<p>Open <code>pages\/[...uri].js<\/code> in a code editor. Our single blog post pages will be rendered using this file.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> GET_POST = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  query getPost($uri: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    post(id: $uri, idType: URI) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      title<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      ...BlocksField<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  <span class=\"hljs-subst\">${BLOCKS_FIELD}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getStaticProps<\/span>(<span class=\"hljs-params\">context<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> uri = context.params.uri.join(<span class=\"hljs-string\">\"\/\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> client.query({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">query<\/span>: GET_POST,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">variables<\/span>: { uri },\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> post = response?.data?.post;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (!post) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">notFound<\/span>: <span class=\"hljs-literal\">true<\/span> };\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">props<\/span>: { post },\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">revalidate<\/span>: <span class=\"hljs-number\">120<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see that inside of the <code>getStaticProps()<\/code> function, we run the <code>GET_POST<\/code> 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 <code>SinglePost<\/code> component in this same file.<\/p>\n\n\n\n<p><code>SinglePost<\/code> looks like this:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SinglePost<\/span>(<span class=\"hljs-params\">{ post }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { title, blocks } = post;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">article<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"blog-post\"<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>{title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          {blocks<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            ? blocks.map((block, index) =&gt; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Block<\/span> <span class=\"hljs-attr\">block<\/span>=<span class=\"hljs-string\">{block}<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{index}<\/span> \/&gt;<\/span>)<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            : null}<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">article<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Layout<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  );<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see that if we have blocks data to render, we map over the blocks and render a <code>Block<\/code> component for each one.<\/p>\n\n\n\n<p>Now open <code>\/components\/Block.js<\/code> so we can take review the <code>Block<\/code> component. It looks something like this:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Block<\/span>(<span class=\"hljs-params\">{ block }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { attributes, name, innerBlocks } = block;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">switch<\/span> (name) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-string\">\"core\/heading\"<\/span>:\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">HeadingBlock<\/span> {<span class=\"hljs-attr\">...attributes<\/span>} \/&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-string\">\"core\/paragraph\"<\/span>:\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ParagraphBlock<\/span> {<span class=\"hljs-attr\">...attributes<\/span>} \/&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ <span class=\"hljs-doctag\">TODO:<\/span> Account for all other block types here.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">default<\/span>:\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">null<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This component is little more that a <code>switch()<\/code> statement. It takes in the <code>block<\/code> prop, determines which block should be rendered based on its name, then renders the corresponding block component.<\/p>\n\n\n\n<p>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 <meta charset=\"utf-8\">need the <code>innerBlocks<\/code> data passed to them as a prop, in addition to <code>attributes<\/code>. Check out <a href=\"https:\/\/github.com\/WebDevStudios\/nextjs-wordpress-starter\">WebDevStudios&#8217; Next.js WordPress Starter<\/a> to see an example of how to do this.<\/p>\n\n\n\n<p>If you have a 1:1 mapping of blocks to components without the need to wrap any of them in <code>div<\/code>s\/pass additional props\/etc., you could even use <a href=\"https:\/\/gist.github.com\/kellenmace\/63467ce0c7a3629f4a457766640527ca\">this alternative approach<\/a> which uses a JS object to map each block with its corresponding component.<\/p>\n\n\n\n<p>At the top of the <code>Block.js<\/code> file is this GraphQL fragment, which represents the data to be queried for all blocks:<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> BLOCKS_FIELD = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  fragment BlocksField on Post {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    blocks {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      name<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      ... on CoreHeadingBlock {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        attributes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          ... on CoreHeadingBlockAttributes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            ...HeadingBlockAttributes<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      ... on CoreParagraphBlock {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        attributes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          ... on CoreParagraphBlockAttributes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">            ...ParagraphBlockAttributes<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  <span class=\"hljs-subst\">${HEADING_BLOCK_ATTRIBUTES}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  <span class=\"hljs-subst\">${PARAGRAPH_BLOCK_ATTRIBUTES}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Notice that the fragments for individual block types are also interpolated into this template literal. This fragment is used in the <code>[...url].js<\/code> file we saw earlier with the <code>...BlocksField<\/code> syntax.<\/p>\n\n\n\n<p>Now let&#8217;s take a look at an individual block component. Open up <code>components\/blocks\/ParagraphBlock.js<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> PARAGRAPH_BLOCK_ATTRIBUTES = gql<span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  fragment ParagraphBlockAttributes on CoreParagraphBlockAttributes {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    align<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    anchor<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    backgroundColor<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    className<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    content<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    dropCap<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    style<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    textColor<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getClassName<\/span>(<span class=\"hljs-params\">align<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (align === <span class=\"hljs-string\">\"center\"<\/span> || align === <span class=\"hljs-string\">\"right\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`text-<span class=\"hljs-subst\">${align}<\/span>`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"text-left\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ParagraphBlock<\/span>(<span class=\"hljs-params\">{<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  align,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  anchor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  backgroundColor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  className,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  content,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  dropCap,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  style,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  textColor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{getClassName(align)}<\/span>&gt;<\/span>{parseHtml(content)}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>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.<\/p>\n\n\n\n<p>Now take a look at the <code>ParagraphBlock<\/code> component. You can see that we&#8217;re able to restructure the props passed in to pull out all of those same attributes.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Disregard the <code><meta charset=\"utf-8\">parseHtml()<\/code> function the <code>content<\/code> is passed through for the time being; we&#8217;ll discuss that in the next section.<\/p>\n\n\n\n<p>To see examples of many individual block components beyond the ones in this app&#8217;s repo, check out <a href=\"https:\/\/github.com\/WebDevStudios\/nextjs-wordpress-starter\/tree\/main\/components\/blocks\/Gutenberg\">these<\/a> from WebDevStudios&#8217; Next.js WordPress Starter.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Fix Internal Links<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Custom Server-side Block Parser<\/h3>\n\n\n\n<p>Using this method, you\u2019ll 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 <a href=\"https:\/\/github.com\/kellenmace\/headless-block-parser\">Headless Block Parser<\/a> plugin. Follow the steps in the readme to make use of it in your project.<\/p>\n\n\n\n<p>With that plugin in place, an internal link pointing to&nbsp;<code>https:\/\/my-wp-backend.local\/blog\/hello-world<\/code>&nbsp;in the post content will be rewritten to&nbsp;<code>http:\/\/localhost:3000\/blog\/hello-world<\/code>, for example. So make sure that your frontend app\u2019s routing is set up property to accommodate that.<\/p>\n\n\n\n<p>An alternative approach you could take here would be to remove the domain, turning the links into relative URLs, such as&nbsp;<code>\/blog\/hello-world<\/code>. If you go that route, just be careful to account for all possible URL permutations\u2013 those that contain anchor links or query string parameters, those that point to the homepage (<code>\/<\/code>), and so on.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Convert Anchor tags to Link Components<\/h3>\n\n\n\n<p>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\u2019t quite enough. Although the internal links now point to the correct URL, they\u2019re still just plain ol\u2019 anchor tags (<code>&lt;a&gt;<\/code>). That means that when the user clicks one, a full page reload will be triggered rather than a route change using the SPA framework\u2019s router. Let\u2019s see how we can fix that and turn them into&nbsp;<code>Link<\/code>&nbsp;components instead using a JavaScript-based HTML parser.<\/p>\n\n\n\n<p>If you open up the <code>package.json file<\/code>, you&#8217;ll see that the <a href=\"https:\/\/www.npmjs.com\/package\/html-react-parser\" target=\"_blank\" rel=\"noreferrer noopener\">html-react-parser<\/a>&nbsp;library has been installed.<\/p>\n\n\n\n<p>Head over to <code>\/lib\/parser.js<\/code>&nbsp;now to see how it&#8217;s used.<\/p>\n\n\n<pre class=\"wp-block-code language-jsx\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> parse, { domToReact } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"html-react-parser\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">parseHtml<\/span>(<span class=\"hljs-params\">html<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> options = {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">replace<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">{ name, attribs, children }<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/\/ Convert internal links to Next.js Link components.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> isInternalLink =\n<\/span><\/span><span class='shcb-loc'><span>        name === <span class=\"hljs-string\">\"a\"<\/span> &amp;&amp; attribs&#91;<span class=\"hljs-string\">\"data-internal-link\"<\/span>] === <span class=\"hljs-string\">\"true\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (isInternalLink) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{attribs.href}<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> {<span class=\"hljs-attr\">...attribs<\/span>}&gt;<\/span>{domToReact(children, options)}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        );<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      }<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    },<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  };<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  <span class=\"hljs-keyword\">return<\/span> parse(html, options);<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">}<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The&nbsp;<code>parse()<\/code>&nbsp;function that&nbsp;<code>html-react-parser<\/code>&nbsp;provides parses the string of HTML into nodes. By passing in the&nbsp;<code>options<\/code>&nbsp;object with a&nbsp;<code>replace()<\/code>&nbsp;callback function inside, we tell the parser that when it encounters an anchor tag with a&nbsp;<code>data-internal-link<\/code>&nbsp;data attribute of&nbsp;<code>true<\/code>&nbsp;(an internal link), replace it with a Next.js&nbsp;<code>Link<\/code>&nbsp;component.<\/p>\n\n\n\n<p>Now we can then make use of this new&nbsp;<code>parseHtml()<\/code>&nbsp;function we created in our individual block components, like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> parseHtml <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/parser\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">ParagraphBlock<\/span>(<span class=\"hljs-params\">{<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  align,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  anchor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  backgroundColor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  className,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  content,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  dropCap,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  style,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">  textColor,<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-params\">}<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{getClassName(align)}<\/span>&gt;<\/span>{parseHtml(content)}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span><\/span>;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As a result of this work, our site visitors will be able to click on an internal link inside of a block&#8217;s content and experience an instantaneous route change via Next.js\u2019 router, with no more full page reload.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Other Uses<\/h4>\n\n\n\n<p>The example above that shows converting internal anchor tag links to&nbsp;<code>Link<\/code>&nbsp;components is one use-case. You could use a parsing library to render components for other nodes inside of your blocks&#8217; content as well though, if needed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Style Blocks<\/h2>\n\n\n\n<p>In the first &#8220;Render Blocks as HTML&#8221; post in this series, we saw how it&#8217;s possible to import several stylesheets from WordPress core that provide base styles for the blocks HTML.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trade-off: Control vs. Ease of Implementation<\/h2>\n\n\n\n<p>As you can likely see, this approach gives developers a <em>ton<\/em> 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.<\/p>\n\n\n\n<p>In the first &#8220;Render Blocks as HTML&#8221; post in this series, I described how it&#8217;s possible to use an HTML parsing library to replace a <em>few<\/em> HTML nodes with components, such as the <code>Link<\/code> 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.<\/p>\n\n\n\n<p>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 <a href=\"https:\/\/www.wpgraphql.com\/2021\/03\/09\/gutenberg-and-decoupled-applications\/#gutenberg-blocks-as-html\">Gutenberg and Decoupled Applications<\/a> post on the WPGraphQL blog to determine if it&#8217;s right for your project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>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&#8217;s a helpful piece to reference reference if you choose to implement this approach in your own headless WordPress projects.<\/p>\n\n\n\n<p>Do you have any questions about this method of rendering Gutenberg blocks content? Please reach out to let us know!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":8,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_EventAllDay":false,"_EventTimezone":"","_EventStartDate":"","_EventEndDate":"","_EventStartDateUTC":"","_EventEndDateUTC":"","_EventShowMap":false,"_EventShowMapLink":false,"_EventURL":"","_EventCost":"","_EventCostDescription":"","_EventCurrencySymbol":"","_EventCurrencyCode":"","_EventCurrencyPosition":"","_EventDateTimeSeparator":"","_EventTimeRangeSeparator":"","_EventOrganizerID":[],"_EventVenueID":[],"_OrganizerEmail":"","_OrganizerPhone":"","_OrganizerWebsite":"","_VenueAddress":"","_VenueCity":"","_VenueCountry":"","_VenueProvince":"","_VenueState":"","_VenueZip":"","_VenuePhone":"","_VenueURL":"","_VenueStateProvince":"","_VenueLat":"","_VenueLng":"","_VenueShowMap":false,"_VenueShowMapLink":false,"footnotes":""},"categories":[23],"tags":[],"class_list":["post-282","post","type-post","status-publish","format-standard","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders<\/title>\n<meta name=\"description\" content=\"Learn how to render Gutenberg blocks in your headless WordPress site as React\/Vue\/Svelte components using the WPGraphQL Gutenberg extension.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders\" \/>\n<meta property=\"og:description\" content=\"Learn how to render Gutenberg blocks in your headless WordPress site as React\/Vue\/Svelte components using the WPGraphQL Gutenberg extension.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2021-09-01T02:36:29+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-04-19T14:53:50+00:00\" \/>\n<meta name=\"author\" content=\"Kellen Mace\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:site\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Kellen Mace\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/\"},\"author\":{\"name\":\"Kellen Mace\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/e6e62698d757a8421cc9723ffa8b1be3\"},\"headline\":\"Gutenberg in Headless WordPress: WPGraphQL Gutenberg\",\"datePublished\":\"2021-09-01T02:36:29+00:00\",\"dateModified\":\"2023-04-19T14:53:50+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/\"},\"wordCount\":1734,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/\",\"name\":\"Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"datePublished\":\"2021-09-01T02:36:29+00:00\",\"dateModified\":\"2023-04-19T14:53:50+00:00\",\"description\":\"Learn how to render Gutenberg blocks in your headless WordPress site as React\\\/Vue\\\/Svelte components using the WPGraphQL Gutenberg extension.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Gutenberg in Headless WordPress: WPGraphQL Gutenberg\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"name\":\"Builders\",\"description\":\"Reimagining the way we build with WordPress.\",\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\",\"name\":\"WP Engine\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"width\":348,\"height\":68,\"caption\":\"WP Engine\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/wpebuilders\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCh1WuL54XFb9ZI6m6goFv1g\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/e6e62698d757a8421cc9723ffa8b1be3\",\"name\":\"Kellen Mace\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g\",\"caption\":\"Kellen Mace\"},\"description\":\"Kellen Mace is the Manager of the Developer Relations team at WP Engine. He likes building modern web apps with SvelteKit, TypeScript, Tailwind, and AI tools.\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/kellen-macewpengine-com\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders","description":"Learn how to render Gutenberg blocks in your headless WordPress site as React\/Vue\/Svelte components using the WPGraphQL Gutenberg extension.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/","og_locale":"en_US","og_type":"article","og_title":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders","og_description":"Learn how to render Gutenberg blocks in your headless WordPress site as React\/Vue\/Svelte components using the WPGraphQL Gutenberg extension.","og_url":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/","og_site_name":"Builders","article_published_time":"2021-09-01T02:36:29+00:00","article_modified_time":"2023-04-19T14:53:50+00:00","author":"Kellen Mace","twitter_card":"summary_large_image","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Kellen Mace","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/"},"author":{"name":"Kellen Mace","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/e6e62698d757a8421cc9723ffa8b1be3"},"headline":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg","datePublished":"2021-09-01T02:36:29+00:00","dateModified":"2023-04-19T14:53:50+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/"},"wordCount":1734,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/","url":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/","name":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"datePublished":"2021-09-01T02:36:29+00:00","dateModified":"2023-04-19T14:53:50+00:00","description":"Learn how to render Gutenberg blocks in your headless WordPress site as React\/Vue\/Svelte components using the WPGraphQL Gutenberg extension.","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/gutenberg-in-headless-wordpress-wpgraphql-gutenberg\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"Gutenberg in Headless WordPress: WPGraphQL Gutenberg"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/builders\/#website","url":"https:\/\/wpengine.com\/builders\/","name":"Builders","description":"Reimagining the way we build with WordPress.","publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/builders\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/wpengine.com\/builders\/#organization","name":"WP Engine","url":"https:\/\/wpengine.com\/builders\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","width":348,"height":68,"caption":"WP Engine"},"image":{"@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/wpebuilders","https:\/\/www.youtube.com\/channel\/UCh1WuL54XFb9ZI6m6goFv1g"]},{"@type":"Person","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/e6e62698d757a8421cc9723ffa8b1be3","name":"Kellen Mace","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/479f2598f161363ba2e78e1b085782477580ea3d5c4609cc9bce3be4945090d5?s=96&d=mm&r=g","caption":"Kellen Mace"},"description":"Kellen Mace is the Manager of the Developer Relations team at WP Engine. He likes building modern web apps with SvelteKit, TypeScript, Tailwind, and AI tools.","url":"https:\/\/wpengine.com\/builders\/author\/kellen-macewpengine-com\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/282","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=282"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/282\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=282"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=282"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=282"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}