Drupal draft preview for Next.js
Preview unpublished Drupal content in a Next.js site on Pantheon using draft mode and Simple OAuth
This tutorial shows you how to let Drupal editors preview unpublished content in a decoupled Next.js site on Pantheon. An editor clicks Preview in Drupal, lands on the Next.js site, and sees the draft revision rendered by the real front end.
Learning objectives
This tutorial walks you through:
- Creating a Simple OAuth consumer so Next.js can read unpublished content
- Adding the
/api/draftand/api/disable-draftroutes - Fetching the working-copy revision with
resourceVersion - Configuring the Drupal
next_sitepreview URL and entity types
Requirements
- A Next.js 16 site and a Drupal 11 backend on Pantheon — see the Drupal + Next.js Quick Start
drupal/next2.x anddrupal/simple_oauth6.x on the Drupal sitenext-drupal2.x in the Next.js project- Terminus, logged in
The next-drupal/draft entry point — enableDraftMode, disableDraftMode, and getDraftData — only exists in next-drupal 2.x. Version 1.6 predates the App Router and does not export it. Check your version before you start:
How it works
-
An editor clicks Preview on an unpublished node. Drupal builds a signed preview URL pointing at your site's
/api/draftroute, carryingpath,timestamp,secret,plugin,resourceVersion, and locale parameters. -
Your
/api/draftroute hands the request toenableDraftMode(), which POSTs those parameters back to Drupal's/next/draft-urlendpoint for validation. Drupal recomputes the signature and confirms it. The signature uses Drupal's own private key and hash salt, so there is no preview secret to share with Next.js. -
If validation passes, Next.js enables draft mode by setting cookies, stores the draft data, and redirects to the content's real path.
-
Your page reads the draft data, sees a
resourceVersion, and fetches that revision from JSON:API with an OAuth bearer token instead of the published one.
The secret is time-limited and computed from the timestamp, path, and resource version, so a preview link cannot be replayed indefinitely or edited to expose a different node.
Sites installed from demo-nextjs-drupal-backend with version 1.1.0 or later of the pantheon_nextjs_demo recipe already have the role, scope, consumer, and per-content-type preview configured below. Skip to Add the draft routes.
Create the OAuth consumer
Next.js needs authenticated access to read unpublished content. Simple OAuth uses roles as scopes.
Create a role
-
Go to
/admin/people/rolesand click + Add role. -
Name it
Next.js preview, with the machine namenextjs_preview, and save. -
Assign it these permissions at
/admin/people/permissions:- View published content
- Bypass content access control
This permission lets Next.js read unpublished nodes and revisions. It applies only to authenticated requests made with this consumer's token, not to anonymous traffic. Keep the client secret in Pantheon Secrets and never expose it to the browser.
Create a user and generate keys
-
Add a user at
/admin/people/createand assign it theNext.js previewrole. -
Go to
/admin/config/people/simple_oauthand click Generate keys. Store them outside the docroot — on Pantheon, use the private files path.
Create the scope and consumer
-
At
/admin/config/people/simple_oauth/oauth2_scope/dynamic/add, create a scope:- Machine-readable Name:
nextjs_preview - Grant Types:
Client Credentials - Granularity:
Role - Role:
Next.js preview
- Machine-readable Name:
-
At
/admin/config/services/consumer/add, create a consumer:- Label:
Next.js site - Client ID:
default_consumer - Secret: a strong random value
- Grant Types:
Client Credentials - Scopes:
nextjs_preview - User: the user you created
- Label:
-
Note the client ID and secret — the secret is hashed on save and cannot be read back.
Configure the Drupal site
Set the preview URL
- Go to
/admin/config/services/nextand click Edit next to your site. - Set Preview URL to
https://dev-my-nextjs-site.pantheonsite.io/api/draft. - Click Save.
The Preview secret field on this form is not used for draft mode with next-drupal 2.x; you can leave it as it is.
Enable preview for entity types
- Go to
/admin/config/services/next/entity-types. - Click Configure entity type and select the content type, for example Article.
- On the Draft Mode tab, select Site selector as the Plugin.
- Select your site under Next.js sites.
- Click Save.
Repeat for every content type editors preview.
Add the draft routes
Create the two routes next-drupal provides. Both are GET.
enableDraftMode() validates the request against Drupal, sets the draft cookies, and redirects. You do not need to check the secret yourself — Drupal does it.
Configure the Drupal client
The client needs OAuth credentials to read unpublished content:
Leave withAuth off by default and turn it on per request for draft reads, as the page below does. The consumer's role can read unpublished content, so authenticating every request would let unpublished content into responses that get cached and served to visitors.
Render the draft revision
In draft mode, fetch the working copy rather than the published default. getDraftData() returns the path and resourceVersion Drupal sent:
Draft content must use cache: 'no-store' and carry no cache tags. Caching a draft would store unpublished content in shared storage and risk serving it from the edge CDN to anonymous visitors.
Give editors a visible way out of draft mode. The banner above links to /api/disable-draft, which clears the cookies and returns the site to published content.
Set the secrets
Set the OAuth credentials and the preview secret on the Next.js site, replacing my-nextjs-site:
Push a commit afterward so a new build picks up the values.
Test draft preview
-
In Drupal, create an Article and save it as Unpublished.
-
Click Preview. Drupal redirects you to the Next.js site.
-
Confirm the draft renders and the preview banner appears.
-
Click Exit preview and confirm the unpublished article is no longer reachable.
If preview fails, check the following:
- Redirected to the live URL instead of the draft. Drupal falls back to the live URL for anonymous users and for users with no role beyond Authenticated user. Confirm the editor has a role such as Content editor.
- 422 "The provided secret is invalid." The preview link was altered, or Drupal's hash salt or private key changed after the link was made. Click Preview in Drupal again for a fresh link.
- Every draft read fails with 401, and token requests return "Check the
scopeparameter". The consumer has no scope. Attach thenextjs_previewscope to it. - "The provided secret has expired." Preview links are short-lived by design. Raise Secret expiration at
/admin/config/services/next/settingsif your editors need longer, but keep it short. - Draft renders the published version.
resourceVersionis not reaching the JSON:API request, orwithAuthis not set for the draft request. - 403 from JSON:API. The consumer's user is missing the
Next.js previewrole, or the role lacks Bypass content access control.
Check the runtime logs for detail:
Conclusion
Drupal editors can now preview unpublished content in the real Next.js front end, with access controlled by short-lived signed URLs and an OAuth consumer.
To continue: