Drupal
JavaScript
min read
Last update on

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

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

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
Editor
Ananya Rakhecha
Tech Advocate