Decoupling Drupal with Next.js

Table of contents

Introduction

Drupal is undoubtedly a powerful and feature-rich Content Management System (CMS) that has earned its reputation for robust content management capabilities. However, when it comes to the presentation, Drupal’s built-in Twig-based Front End layer sometimes falls short when compared to more modern alternatives like ReactJS or other Front End frameworks. The use of ReactJS components within a Drupal theme can indeed enhance the frontend experience to some degree. Nevertheless, this approach often faces performance limitations inherent to Drupal.

Thankfully, Drupal recognized this limitation many years ago and has offered a different type of architecture known as “Decoupled Drupal”. In this article, we’ll dive into how to combine Drupal’s strong content management capabilities with the smooth user experience of ReactJS by leveraging the power of the Next.js React framework.

Next.js

Before jumping into the integration of Drupal with Next.js, let’s first understand what Next.js provides. Next.js is an open-source React Framework developed by Vercel and provided under the MIT License. It offers a range of additional structures, features, and optimizations built on top of standard React.js, simplifying web application development and operation.

Next.js is a JavaScript framework but provides a TypeScript-first development experience for building React applications. It comes with built-in TypeScript support and a full set of Next.js-specific types. It also has a great feature that at the start of a development server, it will detect the use of TypeScript on your project and install the required packages, populate the tsconfig.json file, and create the next-env.d.ts file.

Another advantage of Next.js over other JS frameworks is that it comes packed with server-side rendering, automatic code splitting, and static site generation capabilities that help you create SEO-friendly and fast web applications. Its intuitive routing system and dynamic loading ensure seamless navigation and optimized loading times for your users.

Drupal as a headless CMS

Drupal is a highly versatile CMS that traditionally follows a unified architecture, handling both content management and front-end rendering. However, it can also be utilized as a headless CMS, focusing solely on content management and exposing an API for external applications to consume. This approach is commonly known as “Decoupled Drupal.”

The Drupal community has dedicated substantial efforts to transform Drupal into an API-first CMS. This means that the data stored and managed by Drupal can be easily accessed by other software, such as a Next.js site in our case.

Drupal Core has long supported Rest API and JSON API functionalities, providing seamless integration options. Additionally, you can enhance Drupal’s API capabilities by incorporating GraphQL support as a contributed module

In this article, we will focus on JSON API, but it is possible to use GraphQL to connect Drupal with a Next.js site as well.

Next.js for Drupal

What makes it possible to connect Drupal and Next.js is a project called Next.js for Drupal. This solution includes a Drupal module, a Node.js package featuring a DrupalClient class to facilitate interactions with Drupal, and two starter Next.js projects. These starters are specifically designed to assist developers in creating applications that seamlessly integrate with Drupal through either JSON API or GraphQL.

This project also provides good documentation and examples for different scenarios. The project is sponsored by Chapter Three, and they, along with the community, did a great job providing all the required code and documentation to build a Next.js application with Drupal as the source of content.

The Drupal Module

The Drupal module provided by the project Next.js for Drupal enables features such as Incremental Static Regeneration and Preview mode of drafts and content (not yet published on the Next.js site).

The first thing you have to do is install the Next.js for Drupal composer package that contains the required Drupal modules. You can easily do this on an existing Drupal 9 or 10 website, or you can create a new Drupal instance from scratch to act as the CMS of your Next.js site.

composer require drupal/next

You will also have to apply two patches to fix a couple of existing issues with some of the dependencies of this module.

"extra": {
  "patches": {
    "drupal/subrequests": {
      "Get same results on different request": "https://www.drupal.org/files/issues/2019-07-18/change_request_type-63049395-09.patch"
    },
    "drupal/decoupled_router": {
      "Unable to resolve path on node in other language than default": "https://www.drupal.org/files/issues/2022-11-30/decouple_router-3111456-resolve-language-issue-58.patch"
    }
  }
}

In order to apply patches to your composer dependencies you will need a plugin for Composer. If you are starting a new Drupal project or your existing project doesn’t have this composer plugin installed yet, you can install it with the following command:

composer require cweagans/composer-patches

The Next.js for Drupal project documentation explains how to do this in detail in its Quick Start Guide.

To set up the integration, you need to enable two Drupal modules: the Next module and either the Next.js JSON:API or Next.js GraphQL module, depending on your preferred method of connecting Drupal and Next.js. While JSON:API is recommended for most integrations, you can opt for GraphQL if your use case demands it.

You can install these modules through the Drupal admin UI on the Extend screen. Alternatively, if you prefer a command-line approach like myself, you can use Drush to install them with the following command.

drush pmi next next_jsonapi

Path Alias

The next-drupal Next.js plugin and the starter projects use paths on the data fetching functions. So, if you haven’t configured path aliases on your Drupal site yet, you will have to configure them at least for the content types you want to access from Next.js.

Path Alias

That is all that is technically required on the Drupal side to make content available on a Next.js site over JSON:API. There are some things you probably would like to configure anyway, like preview mode, to be able to see drafts and content not yet publicly published look like on the Next.js site. Finally, you will definitely want to have on-demand revalidation. This feature will allow you to update your Next.js site every time certain content is created, updated, or deleted on Drupal.

Go to /admin/config/services/next and click the + Add Next.js site button. Define a label to identify your Next.js Site and the base URL for it. To enable preview mode fill in the preview URL and Preview secret and to enable on-demand revalidation fill in the Revalidate URL and Revalidate secret.

Fetching Data from Drupal

Data fetching from Drupal follows the standard Next.js methods. On your pages, you have an export function that returns all the data needed by the page to be rendered. There are two export functions in Next.js: getStaticProps and getServerSideProps. Both functions are very similar, but getServerSideProps is used for Server-Side Rendering, and getStaticProps is used for Static Site Generation, including Incremental Static Regeneration.

The Next.js for Drupal project provides a very useful DrupalClient that includes helpers to get JSON:API data from Drupal. There is also support for GraphQL, but that is out of the scope of this article. There are many functions available to fetch resources from Drupal. Here are some examples of the most common.

Get a resource by its ID or Path

If you just want to fetch a single resource for which you already know the type and its UUID, you can use the getResource function.

const article = await drupal.getResource(
  "node--article",
  "dad82fe9-f2b7-463e-8c5f-02f73018d6cb"
);

Type and UUID are required parameters for this function, but there are some optional parameters that could be useful. For example locale can be used to fetch the resource in a specific language.

Similarly, you can get a Drupal resource by knowing its path using the getResourceByPath function:

const node = await drupal.getResourceByPath("/blog/slug-for-article")

Creating Dynamic Routes and Getting Drupal Content

In many cases, for example on a blog, you will define a single React component file that will be used by many pages. In this scenario, you use square brackets to define Next.js dynamic routes. These page files have dynamic segments that are filled in at request time or pre-rendered at build time. For example, in the file pages/blog/[slug].js, [slug] is the dynamic segment that will be replaced for each specific blog post.

Even if the routes are dynamic, if we are using Static Site Generation Next.js has to know which routes are actually part of our application as it needs this information to pre-render each of the pages at build time. In practice, this means that if we are using getStaticProps we also have to define a getStaticPaths function on our page files.

Luckily, Next.js for Drupal also provides a getStaticPathsFromContext function to get all the paths for a Drupal content type.

export async function getStaticPaths(context): Promise<GetStaticPathsResult> {
  return {
    paths: await drupal.getStaticPathsFromContext('node--article', context),
    fallback: "blocking",
  };
}

This will return the paths for all the article nodes in our Drupal backend. If we have to narrow down the paths, we can use the optional params parameter that accepts any JSON:API params, such as filter.

Now we need to get the data for each page, but we don’t know the UUID of each resource. Instead, we will obtain the path from the context of getStaticProps or getServerSideProps functions. Next.js for Drupal provides two functions to achieve this: translatePathFromContext, which returns info about a Drupal path from the context, and getResourceFromContext, which fetches the Drupal resource for the current context.

Let’s see an example of this.

export async function getStaticProps(context) {
  const path = await drupal.translatePathFromContext(context);

  const node = await drupal.getResourceFromContext(path, context);

  return {
    props: {
      node,
    },
  };
}

Preview mode

If we are using getStaticProps, Next.js fetches all the page data from our Drupal CMS and uses static generation to pre-render them at build time. This makes the pages much faster as they do not have to be built for each request and can be cached by a CDN improving performance and SEO.

iFrame preview of Next.js on Drupal

This is great once we have the page content published, but we probably want to know how new pages are going to look on our Next.js Site before we publish them. For this scenario, Next.js has a feature called Preview Mode that allows bypassing Static Generation only for this specific case.

To set this up, you create a preview API route on the Next.js application and a function that handles the request and calls setPreviewData on the response object. This sets some cookies on the browser, turning on the preview mode.

You may also have to update the getStaticProps function on your pages to support preview mode, for example, to users view unpublished pages if context.preview is set to true. The getStaticProps function on the pages provided by the starter project already supports preview mode. Usually, getStaticProps is called only at build time, but when the cookies to enable preview mode are set, getStaticProps is called at request time. Depending on your application you may want to fetch different data depending if the preview mode is enabled or not.

Preview Mode

The Drupal module can embed the site preview with an iFrame in the entity page. You can also have a link to the Next.js site and see the preview there on a separate tab or window. This feature even supports revision previews, draft content, and content moderation.

Incremental Static Regeneration

Usually, when you work with static site generation, you create static pages for your entire site during build time. This is great for performance but has the downside that when you want to add a page or make an update to an existing one, you have to rebuild the whole site.

Next.js offers a feature called Incremental Static Regeneration that allows you to update statically generated pages after you’ve built your site. There are two ways to do this.

Time-based Incremental Static Regeneration

The first and easiest one is by adding the revalidate prop to getStaticProps. This numeric value defines the seconds after a new request to the page will trigger a page regeneration. The user will still see the cached (stale) page, but Next.js will attempt to do a rebuild in the background. If the rebuild is successful the following request will get an updated page.

This means that if we set the revalidate prop to 60 on the getStaticProps of our blog page, Next.js will try to rebuild every blog page each minute.

On-demand revalidation

A second way to do this is with On-Demand Incremental Static Regeneration. This is the method supported by the Next Drupal module, but you can still use time-based regeneration if you prefer.

On-demand revalidation

A second way to do this is with On-Demand Incremental Static Regeneration. This is the method supported by the Next Drupal module, but you can still use time-based regeneration if you prefer.

With On-Demand Incremental Static Regeneration, you only trigger the regeneration of a page when the content on Drupal is updated, created, or deleted.

A revalidate API endpoint must be defined in the Next.js application to receive the request and trigger the revalidation of the correct page. This endpoint is pre-defined in the starter project and receives two parameters: the slug to revalidate and a secret used to validate that the revalidation request comes from a trusted source.

The Drupal module allows you to configure On-demand Revalidation by entity types. The module provides a plugin to trigger a revalidation by path, and it also provides a way to manually define a set of additional paths to revalidate, for example, you may want to also revalidate the blog index page each time a blog post is added, deleted, or updated.

Other features of Next.js for Drupal

The Next.js for Drupal project provides support for other things that are outside the scope of this introductory article but are worth mentioning.

  • Helpers for JSON:API write operations (POST, PATCH, DELETE)
  • Helpers to fetch complete collections of resources
  • Helpers to fetch Drupal menus, views, and Search API indexes
  • Support for Next.js Image component for inline images and media entities in body fields
  • Support for Next.js Link component for inline links in body fields
  • Use of Drupal Webforms within Next.js
  • Multiple Next.js sites built from a single Drupal CMS source

Conclusion

Next.js for Drupal is a great project, well-documented and with a good amount of code samples and starters for you to integrate a Drupal backend with a Next.js website or application. The JSON API support is complete and robust, and the additional features like support for draft previews and on-demand incremental static regeneration make it one of the best alternatives if you want to start a new Drupal decoupled project.