Search by

pantheon-systems-ps / pantheon_nextjs_multi_demo

willjackson

Vertical-agnostic backbone for decoupled Next.js-on-Pantheon demos: content model, JSON:API, OAuth, and the Next.js module wired for per-site association.

Package info

github.com/pantheon-systems-ps/pantheon_nextjs_multi_demo

Type:drupal-recipe

pkg:composer/pantheon-systems-ps/pantheon_nextjs_multi_demo

Statistics

Installs: 16

Dependents: 2

Suggesters: 0

Stars: 1

1.0.0 2026-07-28 21:04 UTC

This package is not auto-updated.

Last update: 2026-11-04 20:27:34 UTC


README

Vertical-agnostic backbone for a decoupled Drupal + Next.js architecture on Pantheon. It configures a standard Drupal site as a headless content backend that can serve one or many independent Next.js front ends from a single install.

Apply this recipe first, then apply one or more vertical recipes on top (such as pantheon_nextjs_edu_demo or pantheon_nextjs_commerce_demo) to register a front end and its content.

Requirements

  • Drupal core 10.4 or newer.
  • The following contrib modules, available via Composer: drupal/next, drupal/decoupled_router, drupal/consumers, drupal/simple_oauth, drupal/pathauto.

What it configures

  • Modules: Next.js, Next.js JSON:API, Decoupled Router, Consumers, Simple OAuth, Pathauto, plus the core Datetime and Options modules.
  • Shared content model: the Page and Article content types with their fields and form/view displays, plus the field storage shared with the vertical event content types.
  • Per-site association: a field_next_site entity-reference field (target type next_site, unlimited cardinality) on the shared content types, wired to the Next.js module's entity_reference_field site resolver. Each node is tagged with the front end(s) it belongs to and may belong to more than one.
  • Content APIs: JSON:API for content delivery and the IETF linkset endpoint for decoupled navigation menus.
  • Draft preview: the Simple OAuth preview generator, plus the nextjs_preview role and OAuth scope used to read unpublished content. Complete the one-time setup under Draft preview.

This recipe ships no next_site entity and no content; those are provided by the vertical recipes.

Apply

composer require pantheon-systems-ps/pantheon_nextjs_multi_demo
drush recipe recipes/pantheon_nextjs_multi_demo
drush cache:rebuild

Then apply one or more vertical recipes.

How a front end scopes its content

Each front end filters JSON:API by the field_next_site reference:

/jsonapi/node/article?filter[field_next_site.meta.drupal_internal__target_id]=edu

A front end that omits the filter renders all content: one backend serving everything, or many front ends each serving their own slice.

Draft preview

Draft preview lets a front end display unpublished Drupal content. This recipe configures the structural pieces: the Simple OAuth preview generator, the nextjs_preview role, and a matching OAuth scope. The remaining pieces carry secrets or are content entities, so complete them once per site as an administrator.

1. Generate the OAuth keys

Configuration > People > Simple OAuth (/admin/config/people/simple_oauth):

  • Click Generate keys, enter a writable directory outside the web root (a private files path), and confirm. The Public Key and Private Key fields populate automatically.
  • Click Save configuration.

If the Generate keys button is absent, the PHP OpenSSL extension is disabled. Create an RSA key pair manually and enter the two file paths in the Public Key and Private Key fields.

2. Create the preview service user

People > Add user (/admin/people/create):

  • Username: nextjs_preview (any name works).
  • Status: Active.
  • Roles: Next.js preview.

This is the account the preview token acts as.

3. Configure the default consumer

Configuration > Web services > Consumers (/admin/config/services/consumer), edit Default Consumer:

  • Is Confidential?: checked.
  • Secret: a value shared with the front end (its DRUPAL_CLIENT_SECRET).
  • User: the nextjs_preview user from step 2.
  • Scopes: nextjs_preview.
  • Grant types: Client credentials.
  • Access token expiration time: a non-zero value such as 3600. The default 0 makes tokens expire immediately.

The nextjs_preview role and OAuth scope already exist. Select them here; do not recreate them.

Storing the secret on Pantheon

Keep the shared secret out of code with Pantheon Secrets Manager (Terminus 4.2+):

terminus secret:site:set <site> nextjs_client_secret "<secret>" --type=runtime --scope=web
terminus secret:site:list <site>

Read it at runtime in web/sites/default/settings.php and apply it when setting the consumer secret:

if (function_exists('pantheon_get_secret')) {
  $settings['nextjs_client_secret'] = pantheon_get_secret('nextjs_client_secret');
}

Generate the OAuth keys (step 1) into the private files directory. It persists across deploys, while the code directory is read-only on Test and Live.

Front-end configuration

Each Next.js front end authenticates with the consumer and requests the scope:

DRUPAL_CLIENT_ID=default_consumer
DRUPAL_CLIENT_SECRET=<matches the consumer secret>
DRUPAL_OAUTH_SCOPE=nextjs_preview

Each vertical recipe registers its next_site with preview_url set to <front end>/api/draft and revalidate_url set to <front end>/api/revalidate, which the front end implements.

Verify

curl -s -X POST https://<site>/oauth/token \
  -d grant_type=client_credentials -d client_id=default_consumer \
  -d client_secret=<secret> -d scope=nextjs_preview

A successful response returns an access_token with a non-zero expires_in. Then use Preview on an unpublished node in Drupal, and the front end enters draft mode and renders it.