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?
- Decoupled Architecture: Separates the back-end (Drupal) and front-end (Next.js) for increased flexibility.
- Improved Performance: Next.js 15 introduces automatic caching and async rendering for faster load times.
- Better User Experience: Provides a modern React-powered front-end for improved interactivity.
- 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.