Martin Roest
30 januari 2019
A while ago I wrote about Decoupled, Headless and traditional CMS architecture. The post is about choosing which one is right for you. I wrote about the different architecture characteristics and the consequences it has. Knowing the differences will help you pick the right one for you. Based on that post you could think that you have to choose between Decoupled or Headless. But maybe there is more to choose from.
Recently we delivered a project with React and Drupal. The project required faceted search along with CMS capabilities. To meet the requirements we integrated Searchkit with Drupal. Searchkit is a suite of React components with an Elasticsearch back-end.
The faceted search is the main component but we ended up using other React components on the front-end.
We discovered that integrating Searchkit was relatively easy and it was actually quite nice to have this rich user experience of Searchkit and React components. Eventually we started to create more React components on the front-end and replicating that rich UI. On the other hand we did not want to lose the benefits of Drupal. We definitely wanted to benefit from Drupal’s front-end theming/rendering of the website structure like the main layout, navigation and user logins etc.
Progressively decoupled
Knowing that we require CMS capabilities and want to benefit from Drupal’s theming we did not wanted an API-first or Headless approach. However we needed at least to decouple some of the things. We used Drupal’s webservices to build a few API’s and integrate with other applications. For the main layout and navigation structure we used Drupal theming and templates. Searchkit was hooked into a Drupal page template which was rendered from a Drupal page controller. The approach we took is actually called “progressively decoupled”. I believe it’s not a real industry standard. Dries wrote about it back in 2015 and I think he sort-off coined the term. There is even a flowchart that helps you decide the best approach in decoupling Drupal. Although Dries said “progressively decoupled” is the future or at least Drupal’s future, I believe it is more like a transition. I think in the current technology landscape with so many different devices the end goal is to have a full-featured pure API-first headless CMS. Today’s headless CMS-es are not as full-featured as CMS-es like Drupal. However Drupal is continually evolving to a decoupled architecture and is therefore an interesting choice.
React + Drupal integration
So how did we integrate React with Drupal? Let’s take a closer look. For this example I assume you have experience with both Drupal and React. If not read about it first or just skip this part.
In this example we have a Drupal theme that includes a src folder with JavaScript sources. Our projects actually use TypeScript because we believe that helps us meet our quality standards. But, for the purpose of this example let’s stick with JavaScript. The Drupal theme lives in the (usual) web/themes/custom folder. In the root of the project we have package.json that defines all our JS dependencies. Also other setup tools like webpack are in the root of the folder. This results in having our dev tooling all available from the root folder. So typing “npm install” in the project root will install all node_modules. It will however build (npm build) the JavaScript sources in a build folder located in the Drupal theme which is publicly accessible.
The Component.js file basically contains the bootstrapping and rendering of a React component. It is what you expect from a common setup. Like the example below.
We use webpack and Babel to build and compile Component.js into the build/component.js. Now running the component is just a matter of including the javascript file on a page. Now here is where the Drupal plays a role. What we did is define a library in our custom Drupal theme that references the javascript component.js. This allows us to “attach” the file to a template in for example a Drupal render array. Once the library is available we can use preprocess hooks or just #attach to include and run the component in basically any template.
But, the common React bootstrapping like in the above example can cause issues. It expects the root element to be available so we can only run it when the DOM is available. To do this you need the change the bootstrapping a bit and use Drupal’s JavaScript API. Drupal.behaviors enables you to do a jQuery(document).ready() equivalent. This is especially important when using Drupal’s dynamic page cache and BigPipe. So update Component.js to something like:
The next step is to provide data from Drupal in your React components. Assuming you want pass data from entities or configuration. You can use Drupal webservices to build a REST API and have your React component fetch data from that API. Initially we started with a couple of entities that we wanted to pass to React. So we simply encoded the entities in JSON and included that in our Twig template. It looks like this:
You can also use drupalSettings to pass your values. Now it’s just a matter of reading the variable in you React component. Obviously you can improve the bootstrapping a bit and inject the data as props or state into you React component.
Eventually we created React components that were used as FieldWidgets in Drupal’s admin pages. We re-used the components in the Drupal admin to enable configuring the component and show the real-time updated preview.
Conclusion
Progressively decoupled is maybe not the most purest form of architecture and actually adds coupling. It is however a cost effective and pragmatic approach which brings a lot of opportunities. It enables your creativity to create rich UI components integrated in your favorite CMS. On the other hand it is just a pragmatic approach until the Headless CMS-es ecosystem has matured. Nevertheless, it has proven to us to be successful and we will definitely keep using it in future projects.
By sharing this example I hope I have triggered your creativity to improve your projects. If you have any questions or feedback, please contact me!