Note This library is still in development so if there are any blocks not rendering correctly please create a new issue.
Use this library to render WordPress Gutenberg blocks in your Next JS or Gatsby JS sites.
npm i @webdeveducation/wp-block-tools
or
yarn add @webdeveducation/wp-block-tools
You'll also need the following plugins installed in your WordPress site:
- WP GraphQL
- WP GraphQL Blocks
- WP GraphQL Theme Stylesheet
- WP Gatsby (Required when working with Gatsby but not needed when working with Next JS)
There's 2 stylesheets you need to make your WordPress blocks look exactly like a "native" WordPress site.
The first is the general styles for every core WordPress block. You can import this in gatsby-browser.js
like so:
// gatsby-browser.js
import '@webdeveducation/wp-block-tools/dist/css/style.css';
The second is the theme specific styles. We'll first need to query the stylesheet using the WP GraphQL Theme Stylesheet plugin, then write that to a css file and reference that css file. This is best done in createPages in gatsby-node.js
like so:
// gatsby-node.js
const fs = require('fs');
exports.createPages = async ({ graphql }) => {
try {
const { data } = await graphql(`
query ThemeStylesheetQuery {
wp {
themeStylesheet
}
}
`);
fs.writeFileSync('./public/themeStylesheet.css', data.wp.themeStylesheet);
} catch (err) {
console.error(err);
}
};
Then reference this new css file in gatsby-browser.js
like so:
// gatsby-browser.js
import './public/themeStylesheet.css';
import '@webdeveducation/wp-block-tools/dist/css/style.css';
The WP GraphQL Blocks plugin exposes a new field we can query called blocks
for every post or page.
For example we can query the blocks when we createPages
. WordPress doesn't assign ID's to blocks so we also need to generate these ourselves with the assignIds
helper function like so:
// gatsby-node.js
const path = require('path');
const { assignIds } = require('@webdeveducation/wp-block-tools');
exports.createPages = async ({ actions, graphql }) => {
// ... underneath our stylesheet query ...
try {
const pageTemplate = path.resolve(`src/templates/page.js`);
const { data } = await graphql(`
query AllPagesQuery {
allWpPage {
nodes {
blocks
uri
}
}
}
`);
for (let i = 0; i < data.allWpPage.nodes.length; i++) {
const page = data.allWpPage.nodes[i];
let blocks = assignIds(page.blocks);
createPage({
path: page.uri,
component: pageTemplate,
context: {
blocks,
},
});
}
} catch (err) {
console.error(err);
}
};
Finally we can render our blocks with the BlockRendererProvider
component. If we follow the example above when creating pages, we need a component in src/templates/page.js
so make sure that file exists, then we can render our blocks like so:
// src/templates/page.js
import React from 'react';
import { BlockRendererProvider } from '@webdeveducation/wp-block-tools';
const Page = ({ pageContext }) => {
return <BlockRendererProvider allBlocks={pageContext.blocks} />;
};
export default Page;
For this we can use the siteDomain
(the siteDomain
refers to the domain / subdomain that your WordPress site is hosted on) and customInternalLinkComponent
props like so:
// src/templates/page.js
import React from 'react';
import { BlockRendererProvider } from '@webdeveducation/wp-block-tools';
import { Link } from 'gatsby';
const Page = ({ pageContext }) => {
const customInternalLinkComponent = (
{ internalHref, target, rel, className, children },
index
) => {
return (
<Link
key={index}
to={internalHref}
className={className}
target={target}
rel={rel}
>
{children}
</Link>
);
};
return (
<BlockRendererProvider
customInternalLinkComponent={customInternalLinkComponent}
siteDomain="wp.mydomain.com"
allBlocks={pageContext.blocks}
/>
);
};
Note The
siteDomain
doesn't care about the protocol so can be specified without the protocol, or with either http or https, it doesn't matter. For example all these will result in exactly the same behaviour :
siteDomain = 'wp.mydomain.com';
siteDomain = 'http://wp.mydomain.com';
siteDomain = 'https://wp.mydomain.com';
And any of the above will match any internal links that match the domain name, independent of the protocol, so specifying
wp.mydomain.com
will also return links in thecustomInternalLinkComponent
that matchhttp://wp.mydomain.com
andhttps://wp.mydomain.com
.
We can render a custom component for a particular block type by using the renderComponent
prop in the BlockRendererProvider
. For example, let's say we want to force all column blocks to have a border: 5px solid red
. We would also need to be able to render the innerBlocks
for that column block so we can also use the BlockRenderer
component like so:
// src/templates/page.js
import React from 'react';
import {
BlockRendererProvider,
BlockRenderer,
} from '@webdeveducation/wp-block-tools';
const Page = ({ pageContext }) => {
const blockRendererComponents = (block) => {
switch (block.name) {
case 'core/column': {
return (
<div key={block.id} style={{ border: '5px solid red' }}>
<BlockRenderer blocks={block.innerBlocks} />
</div>
);
}
default:
return null;
}
};
return (
<BlockRendererProvider
renderComponent={blockRendererComponents}
siteDomain="wp.mydomain.com"
allBlocks={pageContext.blocks}
/>
);
};
First of all we need to make the gatsbyImage available for our core/image
block. This can be done in gatsby-node.js
right after we assignIds
:
for (let i = 0; i < data.allWpPage.nodes.length; i++) {
const page = data.allWpPage.nodes[i];
let blocks = assignIds(page.blocks);
blocks = await assignGatsbyImage({
blocks,
graphql,
coreImage: true,
//coreMediaText: true,
//coreCover: true,
});
createPage({
path: page.uri,
component: pageTemplate,
context: {
blocks,
},
});
}
Then we can update our blockRendererComponents
to cater for the core/image
block:
// src/templates/page.js
import { getClasses, getStyles } from '@webdeveducation/wp-block-tools';
import { GatsbyImage } from 'gatsby-plugin-image';
// ...
const blockRendererComponents = (block) => {
switch (block.name) {
case 'core/column': {
return (
<div key={block.id} style={{ border: '5px solid red' }}>
<BlockRenderer blocks={block.innerBlocks} />
</div>
);
}
case 'core/image': {
const { gatsbyImage } = block.attributes;
if (!gatsbyImage) {
return null;
}
return (
<figure key={block.id} className={getClasses(block)}>
<GatsbyImage
image={block.attributes.gatsbyImage}
alt={block.attributes.alt || ''}
style={getStyles(block)}
/>
</figure>
);
}
default:
return null;
}
};
// ...
Note The
getClasses
andgetStyles
helpers will return the associated WordPress classes and styles for this particular block type. For example if we set a rounded border for the image block in WordPress, then this will be returned bygetStyles
so we can apply this to our Gatsby Image. This makes our Gatsby Image render identical to the WordPresscore/image
.
There's 2 stylesheets you need to make your WordPress blocks look exactly like a "native" WordPress site.
The first is the general styles for every core WordPress block. You can import this in styles/globals.css
like so:
// styles/globals.css
@import '~@webdeveducation/wp-block-tools/dist/css/style.css';
The second is the theme specific styles. We'll first need to query the stylesheet using the WP GraphQL Theme Stylesheet plugin, then write that to a css file and reference that css file. This is best done getStaticPaths
in pages/[[...slug]].js
like so:
// apolloClient.js
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: process.env.WP_GRAPHQL_URL,
cache: new InMemoryCache(),
});
export default client;
// .env.local
WP_GRAPHQL_URL=http://my-wordpress-site.local/graphql
// pages/[[...slug]].js
import fs from 'fs';
import client from '../apolloClient';
import { gql } from '@apollo/client';
import {
assignIds,
BlockRendererProvider,
} from '@webdeveducation/wp-block-tools';
export const getStaticPaths = async () => {
const { data } = await client.query({
query: gql`
query AllPagesQuery {
themeStylesheet
pages {
nodes {
uri
}
}
posts {
nodes {
uri
}
}
}
`,
});
try {
fs.writeFileSync('./public/themeStylesheet.css', data.themeStylesheet);
} catch (e) {
console.log('ERROR WRITING FILE!', e);
}
return {
paths: [...data.pages.nodes, ...data.posts.nodes].map((page) => ({
params: {
slug: page.uri.substring(1, page.uri.length - 1).split('/'),
},
})),
fallback: false,
};
};
Then reference this new css file in styles/globals.css
like so:
// styles/globals.css
@import '/themeStylesheet.css';
@import '~@webdeveducation/wp-block-tools/dist/css/style.css';
The WP GraphQL Blocks plugin exposes a new field we can query called blocks
for every post or page. WordPress doesn't assign ID's to blocks so we also need to generate these ourselves with the assignIds
helper function.
For example we can query the blocks when we getStaticProps
like so:
// pages/[[...slug]].js
import { assignIds } from '@webdeveducation/wp-block-tools';
export const getStaticProps = async (context) => {
const uri = context.params?.slug ? `/${context.params.slug.join('/')}/` : '/';
const { data } = await client.query({
query: gql`
query PageQuery($uri: String!) {
nodeByUri(uri: $uri) {
... on Page {
blocks
}
... on Post {
blocks
}
}
}
`,
variables: {
uri,
},
});
return {
props: {
blocks: assignIds(data.nodeByUri.blocks || []),
},
};
};
Then we can finally render our blocks with the BlockRendererProvider
component like so:
// pages/[[...slug]].js
import { BlockRendererProvider } from '@webdeveducation/wp-block-tools';
const Page = ({ blocks }) => {
return <BlockRendererProvider allBlocks={blocks} />;
};
export default Page;
For this we can use the siteDomain
(the siteDomain
refers to the domain / subdomain that your WordPress site is hosted on) and customInternalLinkComponent
props like so:
// pages/[[...slug]].js
import Link from 'next/link';
const Page = ({ blocks }) => {
const customInternalLinkComponent = (
{ internalHref, target, rel, className, children },
index
) => {
return (
<Link
key={index}
href={internalHref}
className={className}
target={target}
rel={rel}
>
{children}
</Link>
);
};
return (
<div>
<BlockRendererProvider
allBlocks={blocks}
siteDomain="wp.mydomain.com"
customInternalLinkComponent={customInternalLinkComponent}
/>
</div>
);
};
Note The
siteDomain
doesn't care about the protocol so can be specified without the protocol, or with either http or https, it doesn't matter. For example all these will result in exactly the same behaviour :
siteDomain = 'wp.mydomain.com';
siteDomain = 'http://wp.mydomain.com';
siteDomain = 'https://wp.mydomain.com';
And any of the above will match any internal links that match the domain name, independent of the protocol, so specifying
wp.mydomain.com
will also return links in thecustomInternalLinkComponent
that matchhttp://wp.mydomain.com
andhttps://wp.mydomain.com
.
We can render a custom component for a particular block type by using the renderComponent
prop in the BlockRendererProvider
. For example, let's say we want to force all column blocks to have a border: 5px solid red
. We would also need to be able to render the innerBlocks
for that column block so we can also use the BlockRenderer
component like so:
// src/templates/page.js
import React from 'react';
import {
BlockRendererProvider,
BlockRenderer,
} from '@webdeveducation/wp-block-tools';
const Page = ({ blocks }) => {
const blockRendererComponents = (block) => {
switch (block.name) {
case 'core/column': {
return (
<div
key={block.id}
className={getClasses(block)}
style={{ border: '5px solid red', ...getStyles(block) }}
>
<BlockRenderer blocks={block.innerBlocks} />
</div>
);
}
default:
return null;
}
};
return (
<BlockRendererProvider
renderComponent={blockRendererComponents}
siteDomain="wp.mydomain.com"
allBlocks={blocks}
/>
);
};
Note The
getClasses
andgetStyles
helpers will return the associated WordPress classes and styles for this particular block type. For example if we set a rounded border for the image block in WordPress, then this will be returned bygetStyles
so we can apply this to our Next Image. This makes our blocks render identical to the WordPress blocks.
We can update our blockRendererComponents
to cater for the core/image
block:
// pages/[[...slug]].js
import { getClasses, getStyles } from '@webdeveducation/wp-block-tools';
import Image from 'next/image';
// ...
const blockRendererComponents = (block) => {
switch (block.name) {
case 'core/column': {
return (
<div
key={block.id}
className={getClasses(block)}
style={{ border: '5px solid red', ...getStyles(block) }}
>
<BlockRenderer blocks={block.innerBlocks} />
</div>
);
}
case 'core/image': {
return (
<figure key={block.id} className={getClasses(block)}>
<Image
src={block.attributes.url}
width={block.attributes.width}
height={block.attributes.height}
alt={block.attributes.alt || ''}
style={getStyles(block)}
/>
</figure>
);
}
default:
return null;
}
};
// ...