Drupal + Next.js Quick Start
Stand up a decoupled Drupal 11 backend and a Next.js 16 front end on Pantheon using the demo upstreams
This guide gets a decoupled Drupal 11 backend and a Next.js 16 front end running on Pantheon. Drupal serves content over JSON:API, the Next.js App Router renders it, and the Next.js for Drupal (next) module handles draft preview and on-demand revalidation.
Learning objectives
This guide walks you through:
- Creating a Drupal backend from the
demo-nextjs-drupal-backendCustom Upstream - Creating a Next.js site from the
demo-nextjs-drupal-frontendrepository - Connecting the two with the Drupal
next_siteentity and Pantheon Secrets - Verifying that content flows from Drupal to Next.js
Requirements
- A Pantheon workspace on a Gold, Platinum, or Diamond plan — Next.js sites require Multidev
- A GitHub or GitLab account, for the front-end repository
- Install the following:
* Requires logging in after installation.
Architecture
The two halves deploy through different Pantheon models. Keep them straight:
| Part | Repository | Pantheon model | Deploys by |
|---|---|---|---|
| Drupal 11 backend | pantheon-upstreams/demo-nextjs-drupal-backend | Custom Upstream (Integrated Composer) | terminus upstream:updates:apply |
| Next.js 16 front end | pantheon-upstreams/demo-nextjs-drupal-frontend | Next.js site | Git push to the connected repository |
Content flows one direction — Drupal to Next.js — over three channels:
- Content: JSON:API (
/jsonapi/node/article,/jsonapi/node/page, and so on) - Navigation: the Decoupled Menus linkset endpoint (
/system/menu/nextjs/linkset) - Change notifications: Drupal calls the front end's
/api/revalidatewhen content changes, and the front end's/api/draftwhen an editor previews unpublished content
If you are following an older Drupal + Next.js guide under Front-End Sites, that offering is deprecated. This guide targets Pantheon's current Next.js hosting. See How to migrate from Front-End Sites if you have an existing Front-End Site.
Create the Drupal backend
The backend ships as a Pantheon Custom Upstream following the drupal-composer-managed pattern. Its content model and demo content install from two Drupal recipes:
| Recipe | Type | Provides |
|---|---|---|
pantheon-systems-ps/pantheon_nextjs_demo | Site | JSON:API, OAuth, the next / decoupled_router / consumers / simple_oauth / pathauto modules, the Page / Article / Event content types, the nextjs menu, the next_site connection, preview and cache-tag revalidation for each content type, and the nextjs_preview role and OAuth scope |
pantheon-systems-ps/pantheon_nextjs_demo_content | Content | Demo Articles, Events, Pages, Tags, images, and menu links |
-
Add the repository as a Custom Upstream in your Pantheon workspace. See Custom Upstream Usage.
-
Create a site from that upstream. See Create a Composer-managed CMS site. Integrated Composer installs Drupal core, the contrib modules, and the recipe packages.
-
Install the site through the browser. The upstream ships a recipe-driven install profile that applies both recipes, generates the OAuth keys, and attaches the
nextjs_previewscope to the default consumer.
Version 1.1.0 of the pantheon_nextjs_demo recipe added preview and revalidation for each content type and the nextjs_preview role and scope. On a site installed with an earlier version, Drupal will not call the front end or allow draft reads until you configure them. See Drupal cache revalidation and Drupal draft preview.
-
On the installer's Configure front end step, copy the
.envblock it displays. It contains the values your Next.js site needs:
DRUPAL_CLIENT_SECRET is generated during install and displayed only on that screen — it is hashed once stored on the OAuth consumer. If you lose it, reset the secret on the default_consumer consumer at /admin/config/services/consumer and update the matching Pantheon Secret.
Create the Next.js site
-
Fork or clone
demo-nextjs-drupal-frontendinto your own GitHub or GitLab account. -
Create a Next.js site on Pantheon connected to that repository. See the Next.js Hello World Tutorial for the site creation flow.
-
Confirm
package.jsonpins a Node version Pantheon provides:
Pantheon builds the site on every push. Pushing main deploys to Dev; opening a pull request creates a Multidev environment.
Set the environment variables
Next.js sites read environment variables from Pantheon Secrets. Set the values from the installer's .env block, replacing my-nextjs-site with your site name:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_DRUPAL_BASE_URL | Drupal backend URL, used in the browser and for server-side fetches |
NEXT_IMAGE_DOMAIN | Drupal host allowed for next/image (host only, no scheme) |
DRUPAL_CLIENT_ID | Simple OAuth consumer client ID |
DRUPAL_CLIENT_SECRET | Consumer secret, used for authenticated calls and draft preview |
DRUPAL_REVALIDATE_SECRET | Shared secret for on-demand revalidation — see Drupal cache revalidation |
DRUPAL_PREVIEW_SECRET | Not read by the front end. The installer prints it, but Drupal signs and validates preview links itself — see Drupal draft preview |
To override a value for a single environment, target <site>.<env>:
Secrets are picked up at build time, so push a commit or pass --rebuild to trigger a new build after changing them.
Point Drupal at the front end
Drupal tracks each front end as a next_site entity holding its base, preview, and revalidate URLs.
-
On your Drupal site, go to Configuration → Web services → Next.js (
/admin/config/services/next). -
Edit the
nextjssite and set:- Base URL:
https://dev-my-nextjs-site.pantheonsite.io - Preview URL:
https://dev-my-nextjs-site.pantheonsite.io/api/draft - Revalidate URL:
https://dev-my-nextjs-site.pantheonsite.io/api/revalidate
- Base URL:
-
Click Save.
Dev, Test, Live, and each Multidev have distinct URLs. Either create a next_site per environment on the matching Drupal environment, or set these URLs per Drupal environment through configuration overrides in settings.php.
Verify the connection
-
Confirm Drupal is serving JSON:API:
-
Confirm the linkset endpoint returns the
nextjsmenu: -
Open your Next.js site. The home page,
/posts,/events, and/tagsshould render Drupal content, with navigation built from thenextjsmenu.
If pages are empty, check that NEXT_PUBLIC_DRUPAL_BASE_URL points at a reachable Drupal environment and review the build and runtime logs:
If pages say Drupal can't be reached
Pages that show Drupal Connection Issue, while the curl checks above succeed, usually mean Drupal's CDN is challenging your Next.js server's requests. Pantheon's next-generation Global CDN scores incoming traffic and challenges anything that looks automated, and a server fetching from Drupal is automated traffic. A challenged request gets a 403 HTML page, with the response header cf-mitigated: challenge, instead of JSON.
To confirm, request JSON:API with the same User-Agent your front end sends:
A 403 means the request was challenged.
Don't send a custom User-Agent. A descriptive one such as NextJS Drupal Client is reliably challenged, while Node's default is not. The demo front end sets one in src/lib/drupal-fetch.ts; remove those 'User-Agent' lines.
Send a bot bypass token. This is the supported way to let your own automation through, and it doesn't depend on how a request is scored. Bot bypass tokens are generated per Drupal site and cover all its environments:
Store the current token as a secret on the Next.js site. Use a name without the NEXT_PUBLIC_ prefix, so it stays on the server:
Then send it as the x-pantheon-bot-bypass header on server-side requests to Drupal, only when it's set:
If you use the next-drupal client, pass the header in its headers option. Setting headers replaces the client's defaults, so include them:
Tokens last six months, and an incorrect token is rejected with a 403, so check the value first if requests start failing after a rotation. Never send the token from browser code.
Requests to a Dev environment may return a sandbox interstitial page rather than content. See bypassing the interstitial page with an HTTP header when testing with curl.
Next steps
- Drupal cache revalidation for Next.js — keep the front end in sync when Drupal content changes
- Drupal draft preview for Next.js — preview unpublished content from the Drupal editor
- Deploy to Test and Live environments
- Multidev environments