DEV Community

Cover image for Connecting Gutenberg and Frontity
SantosGuillamot for Frontity Framework

Posted on • Updated on • Originally published at frontity.org

Connecting Gutenberg and Frontity

This is a written version of the "Connecting Gutenberg and Frontity: A Case Study" talk that I gave at the 2020 JavaScript for WordPress Conference. You can find the slides here and watch the full talk on Youtube.

Gutenberg (also known as the block editor) is already being used by many WordPress users. Rolled into WordPress 5.0, it has reinvented the experience of creating and managing content in WordPress.

In this talk, we walk you through the process we followed to rebuild our website (frontity.org) with Gutenberg and Frontity. We share all the challenges and lessons learnt, and what we considered to be the best approach to solve them.

Table of contents

Introduction:

Connecting Gutenberg and Frontity:

Conclusions:


Introduction

Why use Gutenberg and Frontity together?

As you already know, WordPress as a publishing tool is great. That is one of the main reasons why it's widely used for different websites. In addition, Gutenberg opened a new paradigm which is revolutionizing the way we build, edit, and publish online. Its goal is to make the publishing experience even easier. 

By using Gutenberg with a React framework like Frontity, you can benefit from the best of these two worlds.

Why Gutenberg and Frontity asset

First, you will get a great editing experience thanks to WordPress and Gutenberg. Frontity allows you to maintain the publishing workflow with all the awesome functionalities that are already made for the WordPress editor.

On the other hand, because Frontity is based on React, you will be able to create more complex user interfaces while having a great developer experience. In the same way that WordPress usage is increasing each year and is the most popular CMS, React is becoming a popular library for taking over front-end development.

Lastly, thanks to Frontity's unique approach, combining WordPress and React has never been easier. The framework deals with all the setup needed like Babel, WebPack, the WordPress REST API, the routing... so that you can focus on building your React theme.

If you are interested in knowing more about why Frontity can be a great fit for your project as well as our vision, take a look at this introductory talk about the framework that our co-founder Pablo gave at the JavaScript for WordPress Conference.

Goals of the process

Before starting the migration, we defined the project's goals and requirements that we needed to keep in mind during the whole process. This helped us prioritize and make other decisions later.

Explain Frontity better

After discontinuing the development of Frontity PRO (our previous product) in order to focus on Frontity Framework, we hadn't updated our website to reflect the changes to the product. We felt that the new framework wasn't being properly explained. We had been working on new features which were not mentioned in the homepage, and some of the benefits of using Frontity weren't clearly expressed. For these reasons, our first and main goal was to redesign the whole website to explain everything better and start getting more traction.

Make everything editable from Gutenberg

Sometimes when developing a website, you are tempted to hardcode specific parts of the content or layout that you don't plan to update frequently. An example could be the header or the footer of a website. This can be even more tempting in a headless setup, where some aspects might be a bit more difficult to implement.

In our case, we wanted the marketing team or any other member of the team to be able to easily change the website's copy, or to add or delete sections in the future without having to rely on the dev team. Such flexibility was an essential requirement for us. In the end, this is what a CMS like WordPress is for, right?

Create new pages easily

This is closely related to the previous point: we wanted the ability to create new pages in an easy way. Not just simple pages, but also more complex ones in which we could reuse different layout elements. We thought that, with a proper implementation, we should be able to build nice landing pages just by reusing some patterns of the homepage.

We knew this was going to be another requirement at some point (for building the 10up partner page or similar ones, for example). And again, we didn't want anyone to have to depend on the dev team to do this.

Find the best way to connect Gutenberg and Frontity

Frontity aims to solve all the challenges that a headless setup may cause. For this reason, we want to ensure that Frontity's integration with the WordPress block editor is as smooth as possible. This was the perfect opportunity to understand it better, learn from our own experience, and work on solutions for any issue we might encounter.

Connecting Gutenberg and Frontity

How did we implement it?

We based the implementation on 4 important aspects and reasons.

  1. Adapting Frontity to Gutenberg. This allowed us to use the block editor just as it is.

  2. Block patterns. This enabled us to reuse layout elements in different parts of the website easily.

  3. Template parts. This made it possible to edit common elements that are used in all pages (e.g. the header or footer).

  4. @frontity/html2react. This allowed us to add React logic to any HTML element we wanted.

1. Adapting Frontity to Gutenberg

1.1. How Gutenberg works?

In order to find the best way to connect Frontity and Gutenberg, we had to understand first how Gutenberg works:

Gutenberg flow asset

Add any block from the WordPress editor

Just as they do in any WordPress site, users can create their desired layout using the block editor. They first select the blocks they are going to use, and then define their specific properties. There are Gutenberg blocks for almost everything, and each block has different properties. For example, the paragraph block has a property to change the text colour, and in the image block you'll find properties to change the image's size or alignment. Similarly with the video block, which has different properties to change the controls or activate the autoplay functionality.

In our case, we didn't find anything that we couldn't already do with the built-in Gutenberg blocks. This means that we didn't have to create any custom blocks for our website, which made the implementation much easier.

Gutenberg outputs the HTML adding some classes

Once the user saves a post, Gutenberg outputs the HTML and adds different classes to match each block's properties defined by the user. It doesn't output the block attributes but valid HTML.

This is important because, in the end, both Gutenberg and React are basically just HTML. In other words, we don't have to link each block with React. The Gutenberg HTML, with the classes it adds, is available in the REST API and is fetched by Frontity automatically. This allows Frontity to work with it in the same way it works with any common post. It doesn't require any further integration. We just have to add React logic to the HTML elements we want.

This is an example of the image block, aligned to the right:

<figure class="wp-block-image alignright size-large">
  <img
    src="..."
    class="wp-image-6616"
    alt="AWSM F1 Frontity Theme screenshot"
    ...
  />
</figure>
Enter fullscreen mode Exit fullscreen mode

Gutenberg adds two css files to match that classes

In order to match the layout defined in the editor, Gutenberg creates two css files which add the logic to style the post. Since Frontity is already getting the HTML from the REST API, this is great because we just have to add these files in our Frontity theme to match the classes output in the HTML. From there, Gutenberg is already supported, and we simply have to add React logic to the HTML elements we want.

This would be the css that matches the previous example:

.wp-block-image .alignright {
  float: right;
  margin-left: 1em;
}
Enter fullscreen mode Exit fullscreen mode

There are two exceptions that are not included in these files: colour palette and font sizes.

In Gutenberg, you can define a theme palette (a set of colours) in order to select your own colours in the block editor. Gutenberg will create the new class has-$slug-color but this won't be included in the theme.css and style.css. So we have to make it match from within Frontity. We can add our own colours by adding this simple snippet to WordPress:

add_action( 'after_setup_theme', 'prefix_register_colors' );
function prefix_register_colors() {
    add_theme_support(
        'editor-color-palette', array(
            /* Frontity colors */
            array(
                'name'  => esc_html__( 'Frontity', 'nude' ),
                'slug' => 'frontity',
                'color' => '#1f38c5',
            ),
            array(
                'name'  => esc_html__( 'Primary', 'nude' ),
                'slug' => 'primary',
                'color' => '#0f1c64',
            ),
        )
    );
}
Enter fullscreen mode Exit fullscreen mode

The same happens with the different font sizes. You can define your own set of font sizes to replace the default ones, and Gutenberg will add the class has-$slug-font-size with this simple snippet:

add_theme_support( 'editor-font-sizes', array(
    array(
        'name'      => __( 'Large', 'nude' ),
        'shortName' => __( 'L', 'nude' ),
        'size'      => 20,
        'slug'      => 'large'
    ),
    array(
        'name'      => __( 'Medium', 'nude' ),
        'shortName' => __( 'M', 'nude' ),
        'size'      => 16,
        'slug'      => 'medium'
    ),
    array(
        'name'      => __( 'Small', 'nude' ),
        'shortName' => __( 'S', 'nude' ),
        'size'      => 14,
        'slug'      => 'small'
    )
) );
Enter fullscreen mode Exit fullscreen mode

Imagine we have a paragraph, then we change the font size to medium and we select the frontity color, previously included in our theme palette. Gutenberg will output something like this:

<p class="has-medium-font-size has-frontity-color has-text-color>
  My formatted paragraph
</p>
Enter fullscreen mode Exit fullscreen mode

1.2. How to adapt Frontity to this workflow

Knowing this, in order to integrate Gutenberg in your Frontity project you just have to add the styling logic which is lacking, as the HTML output by Gutenberg will be available in the REST API.

This is how we did it. First, we added the content of the theme.css and the style.css files that Gutenberg creates. The content of both files is available in the SVN repository, and you can select any version of Gutenberg you are using. Once we had this logic, we created these two css files in our Frontity theme, and we imported them into the index.js file:

import { connect, css, Global, Head, styled } from "frontity";
...
import gutenbergStyle from "./styles/gutenberg/style.css";
import gutenbergTheme from "./styles/gutenberg/theme.css";

const Theme = ({ state }) => {
  const data = state.source.get(state.router.link);

  return (
    <>
      <Global styles={css(gutenbergStyle)} />
      <Global styles={css(gutenbergTheme)} />
      ...
    </>
  );
};

export default connect(Theme);
Enter fullscreen mode Exit fullscreen mode

After doing this, everything added from Gutenberg was supported except the colours and the font-sizes. As previously mentioned, we had defined our own colour palette and font-sizes.

In order to match the different colours we had created, the first step we took was to add them to Frontity state as well. To do so, we added them to state.theme.colors in the index.ts file of our theme. Now they were accessible from any part of our Frontity project. Apart from this, we created two processors (explained later): one for the text-color, and other for the background-color that extracts the colour which is used and, looking into state.theme.colors, adds the css needed for this.

Moreover, we created a new util named addAlpha (used in these processors) to add opacity to these colours if a specific class is matched. This isn't supported by Gutenberg yet, so we decided to implement it ourselves.

On the other hand, to match the font-sizes, we added the css we wanted for the specific classes. We did it using a processor, but this can be done directly in the css files:

.has-small-font-size {
    font-size: 14px;
    line-height: 20px;
}

.has-medium-font-size {
    font-size: 16px;
    line-height: 24px;
}

.has-small-font-size {
    font-size: 20px;
    line-height: 32px;
}
Enter fullscreen mode Exit fullscreen mode

And that's it. Now that we had connected Gutenberg and Frontity, we just needed to add the React logic to the HTML elements we wanted.

2. Block patterns

From the Gutenberg side, we realised that we were using the same layouts in different parts of the website. We wanted to be able to reuse them in an easy way, not only for the existing pages but also for future ones. After some research, we decided to use Block Patterns for this purpose.

Block patterns are basically a group of Gutenberg blocks that have been combined together to create a page layout. Once you have created a block pattern, it can be reused throughout your website. That is, you can combine different blocks with different properties, create a pattern and placed it anywhere you like. Then you will only have to change the content within it.

Block Patterns asset

A good example of a block pattern can be found in the "Why Frontity?" section. As you can see, all the feature items follow the same pattern: they have an icon with a squared background, a heading 4, and a paragraph with medium font size. Instead of building each item from scratch, we created a block pattern with the required properties and reused it to list each of the different features.

2.1. How to create block patterns

Here's the code that you can include in your functions.php or in a Code Snippet. However, for a more detailed explanation, please check the Block Patterns documentation.

<?php

register_block_pattern(
   'pattern-slug',
   array(
       'title'   => __( 'Pattern name' ),
       'content' => '...Gutenberg HTML...',
   )
);
Enter fullscreen mode Exit fullscreen mode

After adding this simple function, the patterns that you've created will appear in the Gutenberg editor. In order to create the HTML, what we did first was to create the patterns using the visual editor. Then we changed the properties that we needed. Once we had considered them finished, we switched from the visual to the code editor and copied and pasted the HTML. Doing things this way made everything much easier.

For this you don't have to do anything in your Frontity project. This only affects the Gutenberg editing experience.

2.2. How to use them

Once we had created the patterns, it was really easy to use them in the Gutenberg editor. On the same button that you use to add blocks there's a new tab named Patterns where you can find the ones that were previously defined.

How to add block patterns asset

3. Template parts

The Gutenberg plugin in its experimental version creates a new Custom Post type named Template Parts, which is an important part of block-based themes. We decided to use it for the common elements on every page, such as the footer or the header.

Template parts asset

As they are a CPT, we can edit them just as we do with any other post or page, and WordPress then outputs the content in the REST API. For example, if we create a Template Part for the footer using WordPress, we can fetch the content in this endpoint: https://frontity.org/wp-json/wp/v2/template-parts?slug=footer.

After creating all the template parts that we needed, the next step was to fetch them in our Frontity app and make sure they were available for every page. In order to achieve this, we had to:

  1. Include the Template Parts CPT in the frontity.settings.js file. As we're including /blog/ for the post permalinks, we had to add that as well.
  2. Create an array in the index.js file of our theme to define all the template parts that we wanted to fetch.
  3. Create a Frontity action to fetch all these template parts Before Server Side Rendering.

At this point, our Frontity state already had the content of all the template parts. Now we just had to create the React components that would consume this content. For example, we created the <Footer /> component for the footer template part.

4. @frontity/html2react

Finally, in order to add React logic to just the HTML elements where we wanted to create a more complex interface, we used the @frontity/html2react package. This package not only parses all the HTML content, but also allows you to hook into any HTML element that you want to extend or modify.

html2react asset

It does this based on two important aspects: nodes and processors.

4.1. html2react nodes

Nodes are the JavaScript objects the package returns once you have hooked into an HTML element with all the information about it. Let's imagine we have this small piece of HTML and we hook into it:

<div class="wp-block-group jsforwp">
   <h4>Heading</h4>
   <p>Paragraph</p>
</div>
Enter fullscreen mode Exit fullscreen mode

What we are going to get in our React app is this object:

{
 "type": "element",
 "component": "div",
 "props": { "className": "wp-block-group jsforwp" },
 "children": [ 
   { heading-object },
   { paragraph-object }
 ]
}
Enter fullscreen mode Exit fullscreen mode

As you can see, all the information about the HTML element is still available. We have the HTML tag and all its attributes, so we can do whatever we want. We even have the children object and, although it's not included here because it's a simple example, we could find the properties of the parent attribute as well.

4.2. html2react processors

Processors are the place where you specify which HTML element you want to hook into, and how you want to modify it. For a deeper explanation, please take a look at our docs. Below is a very quick summary:

  • name: just the name of the processor.
  • priority: this allows you to select which processors should run before the others.
  • test: this is the function to select which HTML elements you want to hook into. It's basically an if statement. If test is true, then the processor runs.
  • processor: this is the function where you apply any modification you want. Here you can change the styles, add new children, or change it into a full React component for example.

Here's a quick example based on the previous case. Here we are telling our app: if you find an element with the class "jsforwp", then run the processor. And in the processor function we are making some changes.

const jsforwp = {
 name: "jsforwp",
 priority: 10,
 test: ({ node }) =>
   node.type === "element" &&
   node.props?.className?.split(" ").includes("jsforwp"),
 processor: ({ node }) => {
   //Change styles
   node.props.css = css`background: blue;`;

   //Add a new children
   node.children.unshift(Logo);

   //Substitute it for a React Component
   node.component = NewJSforWP

   return node;
 },
};
Enter fullscreen mode Exit fullscreen mode

4.3. Use cases

The above approaches allowed us to add React logic to any element in the content, which is pretty powerful. Let's take a look at some examples on our own website.

Lazy load

In order to improve the performance, we decided to lazy load the heavier elements like videos, iframes, or tweets. This way we don't load them until the user is close to reach them when scrolling. Instead of doing this with PHP, we did it with html2react. For example, we created a processor that hooks onto any video element, and swaps it for a React component that uses a Frontity hook to lazy load it.

import { Element,Processor } from "@frontity/html2react/types";

import LazyVideo from "../components/lazy-video";

export const lazyVideo: Processor<Element> = {
  name: "lazy-video",
  test: ({ node }) => node.type === "element" && node.component === "video",
  processor: ({ node }) => {
    node.component = LazyVideo;

    return node;
  },
};
Enter fullscreen mode Exit fullscreen mode

You can see above that we are importing the <LazyVideo /> component. This is basically the same HTML we received but using the useInView hook, where we are not loading the video until we are 600px above it:

import useInView from "@frontity/hooks/use-in-view";
import React from "react";

const LazyVideo = ({ children, ...props }) => {
  const { ref, inView } = useInView({
    rootMargin: "600px",
    triggerOnce: true,
  });

  return (
    <>
      <video ref={ref} {...(inView && props)}>
        {children}
      </video>
    </>
  );
};

export default LazyVideo;
Enter fullscreen mode Exit fullscreen mode

Web browser layout

We decided to add a top bar to some elements of the website to make it look as if the element is inside a web browser window. This was useful for images or iframes for example, and prevented us from having to create it for every element.

Web browser iframe asset

The top, grey bar and the three dots, which simulate the browser window, aren't added to the image or the iframe directly. Instead, we added this HTML with React by creating this processor:

import TopBar from "../components/window-top-bar";

export const webBrowser = {
  name: "web-browser",
  test: ({ node }) =>
    node.type === "element" &&
    node.props?.className?.split(" ").includes("has-browser-window"),

  processor: ({ node }) => {
    const topFrame: Element = {
      type: "element",
      component: TopBar,
      props: {},
      // we need to pass empty props, because other processors might
      // expect `.props` to exist
    };

    node.children.unshift(topFrame);

    ...

    return node;
  },
};
Enter fullscreen mode Exit fullscreen mode

As you can see, we added the component <TopBar /> to the children array. And if you take a look at the component, you also will see that we just added the HTML for the bar and the dots with some styles:

import { css, styled } from "frontity";
import React from "react";

const Dot = styled.span`
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-left: 5px;
  border-radius: 100%;
  background-color: rgba(15, 28, 100, 0.2);
  vertical-align: baseline;
  margin-top: 12px;
`;

const WindowTopBar = () => (
  <div
    css={css`
      height: 32px;
      padding-left: 10px;
    `}
  >
    <Dot />
    <Dot />
    <Dot />
  </div>
);

export default WindowTopBar;
Enter fullscreen mode Exit fullscreen mode

Now we have the possibility to add this window effect to any element by adding the class has-browser-window.

We are following a similar approach for the buttons and links on our website. Apart from reusing them in different sections, we wanted to display the Frontity monogram logo before the text.

Frontity buttons and links asset

Since we didn't want to have to add it manually to each button and link from within Gutenberg, we instead created a processor to deal with this. This processor adds the Frontity monogram by default unless we add another icon from Gutenberg or specify that we don't want an icon.

Terminal

This is another example (more complex than the previous ones) of how we used use JavaScript with Gutenberg. If you take a look at the different code blocks that we have, they all look similar.

Styled components example

Apart from adding the layout in a similar way to the previous example, we're using a JavaScript library named Prism for the syntax highlighting. Rather than defining the colors for each piece of code from Gutenberg, it's Prism.js which takes care of this and makes sure they look similar. It works with any code block.

And you can go even further. For example, we're using another React library, react-typist, to load the terminal letter by letter in the homepage's hero. While the commands are editable from WordPress, the effect is made with React (with one of the multiple npm packages that are available).

Newsletter

This is the last example and probably the most complex one of our website. At the bottom of each page, we included a form to subscribe to the Frontity newsletter. We also did this with React rather than PHP.

If you fill out the first form (the email address field), a second form will be loaded and then the "Thank you" message. This is connected with our Google Tag Manager account and the email services. Although the form's content is editable from WordPress, the logic to manage all this was, again, made with React.

Conclusions

What went well

In general, we were able to achieve most of our goals. Knowing that the main purpose was to explain Frontity better, we considered it a success and are happy with the end result. In addition, the design implementation was great. Here are some of our conclusions.

✔️ Gutenberg features were really helpful

Although we had already worked with Gutenberg, there were many different use cases to address in this migration and we were somewhat concerned about it. However, some of Gutenberg's latest features, such as the Block Patterns or the Template Parts, were really helpful in connecting Gutenberg with Frontity and facilitated much of the process.

✔️ We managed to make everything editable from WordPress

We can now edit everything from the WordPress editor. Even the command lines, the header or the footer (which in many websites are hardcoded) are editable in our case. This is great because it allows us to leverage the great editing experience and all the benefits of Gutenberg while using Frontity.

✔️ Creating new pages is easy

This is related to the previous point too. Creating new landing pages from scratch, or reusing the block patterns and styles that were already created for the homepage, is pretty easy now. In addition, we don't have to rely on the development team to make these changes. A couple of great examples are the About Us page and the 10up Partner page, both of which we built afterwards without coding anything.

Here's a quick demo of how we created a new page from scratch:

✔️ There was nothing that we couldn't do

In Frontity PRO, our previous product for big publishers, we had already tested this approach and the @frontity/html2react package worked great. But there were a lot of new, different use cases this time that hadn't been developed before. The good news is that, after creating many diverse processors, we were able to do everything with the built-in Gutenberg blocks and html2react. There are still things to improve and there might be use cases that we didn't cover for sure, but the experience has been great so far.

Lessons learnt and next steps

1. In some parts, we struggled to deal with the CSS that Gutenberg adds as well as to overwrite its styles. One of the things that might be causing this problem is that we add all the css directly to our Frontity app. We would like to work on a Frontity - Gutenberg package that deals with the CSS and solves this. This would allow you to select the Gutenberg plugin version styles that you want to import and add the css used in each page. There's an open conversation about this in this Feature Discussion.

2. We decided to use html2react processors for too many things. In the end, using them for every single Gutenberg block that we wanted to modify doesn't seem right. Writing processors for adding styles to HTML classes feels strange, like an extra step between CSS and HTML. If we start a similar process again, we would consider if some of these processors could be moved to global css.

Apart from this, it would be great to have some generic processors for basic tasks, like adding styles. Also for improving the Html2React API by adding a better way to select elements and modify them. We might want to add higher abstractions for common uses cases:

  • CSS-only processors: if we feel that adding css with the processors is the proper approach, it would be useful to have a nicer API while keeping everything hackable. We are already talking about this here.

  • Transform parent-child HTML nodes into parent-child React components: this would ease the way we add React logic to some elements. There's an ongoing conversation here.

  • Reuse processors between different projects: sometimes there are interesting processors that could be reused in different projects. For example, the way we are importing the Prism.js library for the code blocks, could be useful for other users. We are also discussing about the best way to share them here.

3. Gutenberg is still in an early stage and we had to code functionalities which might be solved by the block editor in the future. For example, the util we created for adding opacity to the colors it's something that could be added from Gutenberg.

4. Finally, we want Frontity to be updated with all the upcoming Gutenberg functionalities. Some of its latest features, such as the Block Patterns or the Template Parts, have already been very useful for us. We will keep track of the updates and continue exploring the best way to integrate them with Frontity.

Actually, we are currently researching the block-based themes and the Full Site Editing experience the Gutenberg team is working on. We really like what they are achieving, and it would be really powerful to integrate Frontity with a block-based theme.


Closing words

We hope this post gives you a better understanding of how we integrated Gutenberg and Frontity, and how they can work together. We do believe this is a powerful approach to leverage the power of the block editor as well as to combine the best of WordPress and React.

In order to fully understand the html2react package, we recommend you take look at our docs. If you are curious to see frontity.org's code, here's the public GitHub repository.

For any other questions you might have, hit us up on Twitter or our community forum. We will be happy to help!


_This post was originally published at frontity.org/blog.

Top comments (0)