Decoupling AcroMedia.com with Drupal 9 & React
Mike Hubbard

Author

Mike Hubbard

, Front End Lead, Developer

Decoupling Acromedia.com with Drupal 9 & React

How we jumped head-first into decoupling Drupal with a React-based design system built for performance and brand consistency.

It’s funny how things go.

As a company, we’re constantly working in Drupal: Building, planning, and helping maintain various modules and websites for our clients and the Drupal community. While most of our clients and work are in the latest version of Drupal, our site is still running Drupal 7. Shame on us, right?!

Like many businesses, it’s easy to push aside the upgrade when our internal systems and staff comfortably use the existing platform, and everything works like a well-oiled machine. Well, we finally got the buy-in we needed from our internal stakeholders, and we were off to the races. But we didn’t want just another Drupal website.

We wanted to explore the “bleeding edge” and see if we could achieve the holy grail of web development — a CMS-driven back end with a modern, decoupled javascript front end using a component-based design system. Go big or go home!

Our approach to the back end

After a solid planning phase, we settled on an approach for the new site build, and it was time to get going. We decided on Drupal (of course) for the back end since it’s our CMS of choice for many reasons, and we could leverage all of the great work that has already been done with the JSON:API. However, not all of our needs were covered out of the box.

Exploring API-first distributions

When we first started, we spent some time looking at using ContentaCMS for our back end. It’s an API-first Drupal distribution that already contains most, if not all, of the modules we needed. We think ContentaCMS is an interesting tool that will stay on our radar for future projects, but ultimately we decided against it this time due to the number of extra modules that were unnecessary for our particular build. In the end, we started with a fresh Drupal 9 install and only added the modules we needed.

Decoupled menus

Another complication we came across focused on decoupled menus.

To build out a menu on the front end, we needed to be able to get menu items. JSON:API Menu Items was installed and gave us a good starting point by exposing the menus in JSON:API.

Next, we needed to get around the fact that JSON:API relies on UUID for content, but menus use paths. Content also likes to have pretty URLs for navigation via path aliases.

To display these path aliases from the front end and be able to retrieve the correct node, we need some way to resolve the path alias to its correct content.

In comes the Decoupled Router module to solve the problem for us! This module provided the endpoints we needed.

Simplifying the JSON output

You get ALL of the data when you first install JSON:API. While this is great, the JSON output relies on the naming convention of fields either set by Drupal or the developer(s) configuring the backend. Though we control how we name newly added fields, we don’t have that control over existing fields. For example, the User entity's “mail” field, which is intended to be the user’s email address, is not easy to understand without knowing the data within the field. We renamed fields as needed for a better developer experience by creating EventSubscribers that tap into the JSON:API response build events. We also installed the OpenAPI module to give our developers a better high-level look at the available endpoints and their structure instead of gross JSON output.

Our approach to the front end

With our back end sorted out, it’s time to get to the front end. When going decoupled, the front end suddenly becomes much more interesting than just a standard Drupal theme with Twig templates, SASS stylesheets and jQuery. We’re in a whole different realm now with new possibilities. We were 100% on board with having better integration between design and development, where the end result of the design was more than static assets and a 1-hour handoff. Here’s a breakdown of our front-end approach.

A new strategy for consistency between design and development

In the past, creative exercises would provide static assets such as style guides to try and capture the design principles of a web property or brand. While this concept is rooted in good intentions, it never worked well as a project evolved. It’s simply too difficult to keep design and development in sync.

For web development, it’s now understood that the code is a better source of truth, not a style guide. But, we still need ways to involve our designers and have them contribute their knowledge of design and UX to the code being developed. After all, if you leave these decisions up to developers, you’re not going to end up in a good place (and most developers will readily agree with that sentiment).

Luckily for us, applications like Figma, Sketch, and Adobe XD, are leading the way to bridge this gap. While this is still very new territory, there is a lot of exciting progress that is enabling the creation of robust design systems that act as a rulebook and single source of truth that is both easily updated, modular, and readily available for product application development.

After an internal study of available technologies for design, we settled on Figma for our design system creative work. Figma runs in either web or desktop applications and is a vector graphics editor and prototyping tool. The team developing it has done an incredible job pushing the envelope on how creative and development can work together. With Figma, we found it was possible to accomplish the following:

Extracting design tokens into our application

With our tokens living in Figma, we still needed a way to bring those static design token values into our development process.

This is where Figmagic comes in. Figmagic is a nice tool for extracting design tokens and graphic assets from Figma into our application.

Once set up, all we need to do in our local development environment is to run yarn figmagic. This connects to Figma and pulls all our defined tokens into an organized set of ready-to-use files. From there, we import the file we need into our component and use the values as required. If a value needs to change, this is done in Figma. Re-running Figmagic brings in any updated value, which updates any component using it. It works like... magic.

Building reusable front-end components

Going decoupled meant breaking away from Drupal’s Twig templating and theming layer.

For our front-end application, we decided on React for our replacement. React continues to be very popular and well-liked, so we didn’t see any technical advantage in picking anything else. This change brought on some additional challenges: our focus has not been React as a Drupal development company.

We undertook some initial training exercises to get our development team on this project up to speed with the basics. Between this team, our CTO, our CXO, and a senior technical lead, we quickly trained and settled on a suite of tools to achieve our desired outcomes of a component-driven React front end.

Component UI frameworks to increase velocity

For us, the approach to developing the many React components we needed was to settle on an established framework. Like Bootstrap is to HTML/CSS development, several React-based frameworks are available to use as a foundation for development.

It’s always debatable whether this is necessary, but we felt this was the best way to get started as it greatly increases the development velocity of our project while also providing good references and documentation for the developers creating our components.

We researched several and tried a few but eventually found ourselves leaning toward Material UI. There were several reasons for this decision, but mainly we like the fact that it is an established, proven framework that is flexible, well-documented, and well-supported.

With Material UI (MUI), adjusting the base theme using our design tokens from Figma already gave us a good start. Its framework of pre-built components allowed us to make minimal changes to achieve our design goals while freeing up time to focus on our more unique components that were a bit more custom.

This framework will continue to be of great benefit in the future as new requirements come up.

Previewing design system components without Drupal

Without a standard Drupal theme available, we needed somewhere to be able to preview our React components for both development and UX review.

A decoupled front end, and the whole point of the design system, is that the front end should be as back-end agnostic as possible. Each component should stand alone. The front end is the front end and the back end could be anything.

Since we haven’t yet connected the front-end components to Drupal’s JSON:API, we still needed an easy way to view our design system and its components. For this, we used Storybook.

Storybook was made for developing and previewing UI components quickly. As a developer working locally, I install and spin up an instance of Storybook with a single command within our project repo. Storybook then looks at our project folder structure, finds any story files we include as part of all of the components we develop, and generates a nice previewer where we can interactively test out our component props and create examples that developers in the future may need when implementing a component.

Storybook is our design system library, previewer, and developer documentation. When we are done developing locally and push code to our repository, our deployment pipeline builds and deploys an updated Storybook preview online.

Bringing it all together

At this point, we have our back end, and API established. We also have our front-end component library created. Now we need to bring it all together and connect the two.

Matching the Drupal UI to our React components

React components are standalone UI elements and are made up of props that determine their functionality.

So, for instance, a button component could have many variants for how it looks. A button could include an icon before or after the text, or it could be used to open a link or a dialogue window, etc. A simple component like this might have many different props that can be used differently. We decided to use the Paragraphs module to control those props through the Drupal UI.

A paragraph in Drupal is a mini content type for a specific use. In Drupal, we created paragraphs that matched the functionality we needed from our React components so that the Drupal UI would set the component props in the end. This is a bit confusing to set up at first and something that will probably not be nailed down first try, but in the process of setting up the Drupal UI in the context of the React components, you can start to see how the connection between the two is made. At this point, it’s pretty easy to see if a component is missing any props or needs to be refactored.

We also went with Paragraphs because we wanted to give our content creators a more flexible way of creating content. A standard Drupal content type is pretty rigid in structure, but with Paragraphs, we can give content creators a set of building blocks that can be grouped and combined in many different ways to create a wide variety of pages and layouts in a single content type.

If you’re unfamiliar with this style of creating content in Drupal, I suggest you look at this post I wrote a while back. Look at that and imagine React components rendering on the page instead of Drupal templates. That’s essentially what we’re aiming to do here.

An argument against Paragraphs is that it could potentially make page building more time-consuming, given that you don’t have that typical rigid structure of a content type. This is especially true if pages on a site being built typically all follow a similar format. In this case, we still like to use Paragraphs anyway, but we will typically build out unpublished pages that act as templates. We then install the Replicate module, which lets content creators clone the template and use it as a starting point. This makes creating pages easy and consistent while still allowing flexibility when needed. Win-win.

Taking the Next.js step

Since we settled on React, we started our front-end project as a standard react build using the built-in create-react-app command. But, after some additional investigation, we decided that a better option for our use case would be to use the Next.js framework. Given that the corporate site is, at its core, a marketing and blogging website, Next.js allows us to still build the front end using React but gives us some extra capabilities this type of website needs.

One of these capabilities is incoming server-side rendering which we found increases the performance of our application. This also has SEO benefits due to pages being rendered server side and sent to the client, as opposed to the standard client-side rendering most React applications use where the client machine handles processing and rendering.

Component factories are key to interpreting the API data

We were also attracted to the structure and organization of the project when using a framework like Next.js, as well as the dynamic routing capabilities. This significantly simplified the code required to implement our decoupled menu and page routing.

Using the dynamic routes provided by Next.js, we could create “catch-all” routes to handle the ever-growing content in the CMS. By querying the Drupal API, we can get all the available path aliases for our content and feed it to Next.js so it can pre-build our pages. It can use the path aliases to resolve the content specific to the page through the Decouple Router module, which will then feed that data to a component factory to handle the decision as to what high-level React page component is necessary to build a page.

For example, if the data is for a basic page content type, the page factory component returns our page component; if it’s a blog post content type, it returns the blog page component, etc. Each of these components knows how to handle the specific data fed to it, but the factory component is what connects the data to the right page.

The way Paragraphs can be added to a page for composing a piece of content also created a challenge on its own in reading that data and returning the right component. We decided to create another factory component for this. Like the page factory component, this one takes in the paragraph data and determines which React components it needs to render the content.

What’s next in our decoupled adventure

Believe it or not, what we’re building is still a work in progress. While we have most of the tech requirements figured out, we’re still actively putting all of the final pieces in place and fine-tuning the backend UI and modules requirements.

Overall, we’re feeling good about this progress and can see the value of what we’re building for ourselves and the clients we serve. In our opinion, that is based on experience and research, decoupled is the future, and we’re excited to walk this path.

With that said, even once our decoupled site has launched, we still have a lot of work left to do.

  • We still need to get a method to manage Drupal blocks and regions. More factory components will be critical here to interpret that data.
  • Given that our clients are primarily ecommerce retailers in one form or another, we still have the whole ecommerce side of web development to sort out, including product catalogues, add-to-cart forms, carts, wish lists, checkout flows, etc. The list is long.

However, we have a framework that can be repurposed and added to. It finally helps tie together many disciplines of web development, both creative and technical, that are traditionally siloed. It’s also blazing fast and just plain cool. This is an investment not only in our own online business front but in something greater that we hope to share with others through work, community and general knowledge.

Thanks for taking the time to read this. Drop us a line if you want to chat about this initiative further.


Editor's note: This article was originally published on April 7, 2021. It has been updated for freshness, accuracy and comprehensiveness.