Overview
Next.js for Drupal v2.0.0 was released on 2025/2/11.
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
scriptsinpackage.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, thepostinstallscript 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.