Overview

Next.js for Drupal v2.0.0 was released on 2025/2/11.

https://next-drupal.org/

https://next-drupal.org/blog/next-drupal-2-0

When I tried it out, I found that the handling of BASE_PATH required attention, so this is a memo about it.

Environment Variables

The sample environment variables are as follows.

# See https://next-drupal.org/docs/environment-variables

# Required
NEXT_PUBLIC_DRUPAL_BASE_URL=https://site.example.com
NEXT_IMAGE_DOMAIN=site.example.com

# Authentication
DRUPAL_CLIENT_ID=Retrieve this from /admin/config/services/consumer
DRUPAL_CLIENT_SECRET=Retrieve this from /admin/config/services/consumer

# Required for On-demand Revalidation
DRUPAL_REVALIDATE_SECRET=Retrieve this from /admin/config/services/next

When specifying NEXT_PUBLIC_DRUPAL_BASE_URL with a base path included, such as https://site.example.com/xxx, API requests were sent to https://site.example.com/jsonapi/, failing to correctly retrieve resources.

Cause

When checking getResourceCollection where the error was occurring, I found that the buildUrl function used to create the request URL was using new URL(path, this.baseUrl);.


...

buildUrl(path, searchParams) {
    const url = new URL(path, this.baseUrl);
    const search = (
      // Handle DrupalJsonApiParams objects.
      searchParams && typeof searchParams === "object" && "getQueryObject" in searchParams ? searchParams.getQueryObject() : searchParams
    );
    if (search) {
      url.search = stringify(search);
    }
    return url;
  }

...

async buildEndpoint({
    locale = "",
    path = "",
    searchParams
  } = {}) {
    const localeSegment = locale ? `/${locale}` : "";
    if (path && !path.startsWith("/")) {
      path = `/${path}`;
    }
    return this.buildUrl(
      `${localeSegment}${this.apiPrefix}${path}`,
      searchParams
    ).toString();
  }

...

async getResourceCollection(type, options) {
    options = {
      withAuth: this.withAuth,
      deserialize: true,
      ...options
    };
    const endpoint = await this.buildEndpoint({
      locale: options?.locale !== options?.defaultLocale ? options.locale : void 0,
      resourceType: type,
      searchParams: options?.params
    });
    this.debug(`Fetching resource collection of type ${type}.`);
    const response = await this.fetch(endpoint, {
      withAuth: options.withAuth,
      next: options.next,
      cache: options.cache
    });
    await this.throwIfJsonErrors(
      response,
      "Error while fetching resource collection: "
    );
    const json = await response.json();
    return options.deserialize ? this.deserialize(json) : json;
  }

When I asked ChatGPT, I received the following answer.

When using new URL(path, this.baseUrl), if the path is an absolute path (such as /jsonapi), only the host portion of baseUrl is applied, and path prefixes like /xxx are ignored. As a result, API requests are sent to /jsonapi/ instead of the intended https://site.example.com/xxx/jsonapi/.

Solution

ChatGPT suggested using patch-package. Below is the response.


patch-package allows you to apply patches to files within node_modules and maintain the changes.

Steps

  • Install patch-package
npm install patch-package postinstall-postinstall
  • Add the following to scripts in package.json
"scripts": {
  "postinstall": "patch-package"
}
  • Directly edit node_modules/next-drupal/dist/index.js (if you have already made the changes, this is OK as-is)
buildUrl(path, searchParams) {
    const url = new URL("/xxx" + path, this.baseUrl); // Fixed by hardcoding in this case
    const search = (
      // Handle DrupalJsonApiParams objects.
      searchParams && typeof searchParams === "object" && "getQueryObject" in searchParams ? searchParams.getQueryObject() : searchParams
    );
    if (search) {
      url.search = stringify(search);
    }
    return url;
  }
  • Create the patch
npx patch-package next-drupal

A file named patches/next-drupal+<version>.patch will be created.

  • With this setup, when you run npm install, the postinstall script will automatically apply the patch.

Summary

With the above solution, I was able to communicate between Next.js 15’s App Router and Drupal 11.

There may be better approaches, but I hope this is helpful.