Start of Main Content

There's an increasing demand to create decoupled or hybrid experiences (progressively decoupled) on the web. This means treating your back-end system as your data/content storage and using client-side frameworks to power your website's user interface instead of using traditional theming methods.

In the following examples, we take a hybrid approach to rendering interfaces in Drupal 9 using ReactJS. The examples serve as a route and a controller where the entire page is rendered in a ReactJS component. We can also do this with render arrays using Drupal block types, Paragraphs, or Components in Acquia Site Studio to allow editors to add rich experiences to pages at their discretion with zero complexity. The examples follow design and development patterns we've been using since the release of Drupal 8 to implement client-side rendering alongside traditional Drupal page rendering.

The first example is a sitewide search interface. A sitewide search is comprised of a single text input field (typically a full-text search field), one or more facets (taxonomy, search modifiers), and a sidebar of promotional content or 'more like this' or 'top hit' style result. The interface may have the filters, facets, and results arranged in a variety of ways depending on the design.

Traditionally, theming this with Views and exposed filters and getting all the templates and markup right can be tedious. Instead, we can bypass that by creating an API that can be queried and return results in JSON. This leaves it up to React to compose and arrange the interface according to the requirements. It also frees developers from having to wrestle with Views configuration and trying to squeeze data out of it.

Make the most of your Drupal website. Learn about our Acquia experience.

We’ll help you create custom Drupal 9 experiences like hybrid experiences (progressively decoupled) that make it easier for your content authors to efficiently deploy new content.

To achieve that you need to define your search route in Drupal and provide the container ReactJS expects to mount. You start by creating a custom module and provide it a routing file with a single route at "/search", where your sitewide search will be accessible to users:
  path: '/search'
    _controller: '\Drupal\mymodule_search\Controller\SearchController::index'
    _title: 'Search'
    _permission: 'access content'

This maps to a controller that returns just the markup container ReactJS is looking for. Over in your controller, you add the method the route is looking for and it returns a response:


namespace Drupal\mymodule_search\Controller;

use Drupal\Core\Controller\ControllerBase;

 * Provide the response for /search route.
 * @package Drupal\mymodule_search\Controller
class SearchController extends ControllerBase {

  public function index(): array {
    return [
      '#markup' => '<div class="js-search-mountnode" data-id="listing"></div>'


When the page loads that markup is returned by the controller method and ReactJS mounts to it. ReactJS populates the div container with data. In between, the ReactJS app is making requests against other back-end APIs and returns a response in JSON that the ReactJS is expecting.

Below, the ReactJS app loads and makes a request which your back-end makes multiple requests against (multiple data sources) and returns the results back to be rendered:

A search interface for Harvard Library with results powered by React in Drupal.

This route is served from a controller, so Drupal still renders the outermost regions (header and footer) while the entire content area (title and search elements plus results and pagination) is rendered with React.

This same pattern can be used in any project. Wiring controllers in Drupal is very simple due to its foundation on Symfony components and the client-side code is mostly portable, depending on requirements. The back-end and front-end only need to agree on the data exchange in the request and response. Here is another example with reactive filters and facets:

A screenshot of a React powered search on Drupal for NBER.

This approach can also be applied to renderable entities in Drupal 9. Instead of returning straight markup, you can return a render array instead. A render array would allow you to pass multiple items of data, attach libraries, or add cache contexts into the response and defer the variable generation to a custom method before reaching the template. More importantly, content editors would be able to drop components in with regular components so they can mix these rich interfaces with normal content.

All you have to do is tell the component what to do when it's being rendered and it can be added to a site as many times as desired:

    return [
      '#theme' => 'mymodule_custom_theme_function',
      '#items' => $items,
      '#attached' => [
        'library' => 'mymodule/custom_library'

Doing that for a block type, Paragraph, or Component in Site Studio would allow content editors to add these to a site with ease. You can add fields to these types too for extra control, such as allowing them to override labels, titles, per page options, or filter results by content type or taxonomy whatever you'd like.

A screenshot of React powered Paragraphs that correspond with individual building floor plans.

With JSON:API and serialization built into Drupal core it's extremely easy to provide APIs for front-end interfaces so they can power progressively decoupled experiences. These patterns and solutions aren't limited to just search and search results. We have built interactive galleries, rich embeds, virtual tours, data visualizations, and other decoupled integrations powered by ReactJS in Drupal 9. We've also added them to Drupal sites without content authors needing to know anything more than how to drag and drop the components onto their sites.

Reach out to start a conversation on how Velir can partner with you for your Drupal 9 and Acquia platform builds. We'd love to hear from you.


Latest Ideas

Take advantage of our expertise with your next project.