Drupal JavaScript4 min readPosted on June 4, 2025

Seamless Headless Drupal integration with Next.js 15 (App Router)

Seamless Headless Drupal integration with Next.js 15 (App Router)

Drupal is a mature and flexible content management system (CMS) trusted for building structured content models and managing complex workflows. 

On the front end, Next.js is a widely adopted React framework known for its speed, developer experience, and production-grade features. With the release of Next.js 15, the new App Router brings powerful updates, including built-in support for async/streaming, granular caching, and improved routing patterns.

When connecting a decoupled Drupal backend to a Next.js 15 frontend, handling authentication becomes a key step, especially for gated content, personalisation, or editorial workflows. 

This blog covers how to implement authentication in a headless Drupal + Next.js 15 setup using the latest App Router features and best practices around API routes, sessions, and token handling.

Why integrate Drupal with Next.js?‍

  1. Decoupled Architecture: Separates the back-end (Drupal) and front-end (Next.js) for increased flexibility.
  1. Improved Performance: Next.js 15 introduces automatic caching and async rendering for faster load times.
  1. Better User Experience: Provides a modern React-powered front-end for improved interactivity.
  1. Scalability: Using Drupal as a headless CMS makes it easier to scale and manage content across platforms.

Setting up Next.js 15 with App Router

Create a Next.js application

Before starting, ensure you have Node.js installed. Then, run the following command to create a new Next.js 15 project:

npx create-next-app@latest my-nextjs-app --ts --experimental-app
cd my-nextjs-app
npm install

To start the development server:

npm run dev

Your Next.js app should now be running at http://localhost:3000.

Basic Project Structure‍

my-nextjs-app/
├── app/                 # App Router directory
│   ├── layout.tsx       # Root layout (applies to all pages)
│   ├── page.tsx         # Home page (/)
│   ├── about/           # Example route: /about
│   │   └── page.tsx
│   └── api/             # API routes
│       └── user/route.ts
│
├── components/          # Reusable components (e.g., Header, Button)
│   └── Header.tsx
│
├── styles/              # Global and module CSS files
│   ├── globals.css
│   └── Home.module.css
│
├── public/              # Static assets (images, favicon, etc.)
│   └── favicon.ico
│
├── next.config.js       # Next.js configuration
├── tsconfig.json        # TypeScript configuration (if using TS)
└── package.json         # Project metadata and dependencies

Setting Up Drupal as a Headless CMS

Install Drupal new setup

Install and configure Drupal JSON: API

To enable API-based communication between Next.js and Drupal, install the JSON: API module:

ddev composer require drupal/jsonapi
ddev drush en jsonapi -y

Enable CORS for API requests

To allow Next.js to access Drupal’s API, update services.yml:

cors.config:
  enabled: true
  allowedOrigins: ['*']
  allowedHeaders: ['Content-Type', 'Authorisation']
  allowedMethods: ['GET', 'POST', 'OPTIONS', 'PATCH', 'DELETE']

Clear the cache:
ddev drush cr

Fetching Drupal data in Next.js 15 (Using Async and Cache)

Next.js 15 introduces improved async handling and caching. To fetch content from Drupal, install the required packages:

npm install next-drupal drupal-jsonapi-params

Set the variables

# Required
NEXT_PUBLIC_DRUPAL_BASE_URL=https://my-site.ddev.site/
NEXT_IMAGE_DOMAIN=my-site.ddev.site

NEXT_PUBLIC_BASE_URL=http://localhost:3000

Update the image dome in next.config.ts file

Import type { NextConfig } from “next”;
const nextConfig: NextConfig = {
 images: {
   domains: ["my-site.ddev.site"], // Allow images from this domain
   remotePatterns: [
     {
       protocol: "https",
       hostname: "my-site.ddev.site",
       pathname: "/sites/default/files/**",
     },
   ],
 },
};

export default nextConfig;

Then, create a client to fetch data using the new fetch API with caching:

import { NextDrupal } from "next-drupal"
import { DrupalJsonApiParams } from "drupal-jsonapi-params";

const drupal = new NextDrupal(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL);

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; // To fix the development fetch error

export async function fetchArticles() {
 const params = new DrupalJsonApiParams()
   .addFields("node--article", ["title", "id", "body", "field_image", "field_tags"])
   .addInclude(["field_image", "field_tags"])
   .addSort("created", "DESC");

 const response = await drupal.getResourceCollection("node--article", {
   params: params.getQueryObject(),
   cache: "force-cache" // Ensures fast loading
 });
 return response;
}

Rendering the fetched content in Next.js 15:

import { fetchArticles } from "../lib/drupal";
import Image from "next/image";

export default async function Page() {
 const data = await fetchArticles();
 console.log(data);
  return (
   <div className="">
     {data.map((article) => (
       <div key={article.id || article.title}> {/* ✅ Add key to top-level item */}
         <h2 className="article-title">{article.title}</h2>
        
         {article.field_image?.uri?.url && (
           <Image
             src={`${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${article.field_image.uri.url}`}
             alt={article.title}
             width={400}
             height={250}
             className="image"
           />
         )}
        
         {article.body?.value ? <p>{article.body.value}</p> : <p></p>}
        
         {article.field_tags?.length > 0 && (
           <ul>
             {article.field_tags.map((tag, index) => (
               <li key={tag.id || `${tag.name}-${index}`}>{tag.name}</li>
             ))}
           </ul>
         )}
       </div>
     ))}
   </div>
 );
}

Conclusion

By integrating Drupal’s headless capabilities with Next.js 15 and its modern App Router, teams gain a powerful architecture that combines content flexibility with frontend performance. 

Drupal continues to provide a structured, API-first backend for complex content models and editorial workflows, while Next.js 15 brings faster rendering through granular caching, improved async handling, and enhanced developer ergonomics.

This setup creates a future-ready foundation for scaling digital experiences. You can introduce personalisation, real-time updates, static generation for anonymous content, and even edge rendering with Next.js middleware, which also aligns well with the growing demand for composable architectures, enabling easy integration with third-party services like analytics, commerce, or AI-based recommendations.

As headless adoption grows, pairing Drupal with Next.js 15 offers a stable yet forward-looking path, supporting editorial flexibility, high performance, and extensibility for evolving digital products.

Written by

Vasantha Deepika

Frontend Engineer

Editor

Ananya Rakhecha, Tech Advocate

We'd love to talk about your business objectives