Updated: September 30, 2026

Installing P1 on an existing Next.js site

Next.js Integration
Pantheon Hosting
Visual Editor
Developer Workflow

This guide helps you connect P1 to an existing Next.js application deployed on Pantheon. You need access to the Pantheon Next.js site/repo and a developer for this process.

Goal

The goal here is to install P1 on an existing site to be able to use P1 content management and rendering capabilities for existing and new Content on that site, be it the totality of the site or a part of it

What you get, and who does what

By the end, your site has:

  • A Visual Editor at /p1, signed in with your Pantheon P1 account.
  • Published P1 pages served at their real URLs (like /about), right next to your existing pages.
  • Your existing hand-coded pages untouched β€” served exactly as before.
  • Content your team edits in P1 and reviews in a Workstream before it ever reaches your live site.

Every step below is tagged so you can see how close we are to a self-serve experience:

Tag

Meaning

🟒 Anyone

No code β€” done in a dashboard or the Visual Editor. A content creator can do this today.

πŸ”§ Developer

Needs the terminal, source changes, or a deploy on the Pantheon hosting platform. Engineering today.

The Road to self-serve section at the end tracks exactly which πŸ”§ steps stand between today and a fully no-code onboarding.

One guiding principle: use the default SDK and starter kit β€” no custom connectors, no forks. It keeps you on the upgrade path and means your site behaves like every other P1 site.

How P1 works alongside your site

Three ideas clear up almost every question before it comes up.

Code and content are separate. Your Next.js app β€” routes, components, the P1 SDK β€” is code, and it ships through git, one environment at a time. The pages your team edits are content, and they live in P1's collaborative content repository (the "CCR" backend), keyed by a P1 site ID and branch. Content is never in your repo.

Content is shared across your Pantheon next.js environments. Dev, Test, and Live all read the same P1 site and branch, so editing a page updates everywhere at once. That's extremely helpful when you want to stage content changes before they go live; you do it in a Workstream (more on that later), not by juggling environments.

P1 doesn't automatically adopt your existing pages. Installing the editor and moving a specific page into it are two different jobs. The editor works as soon as it's installed; bringing an existing page under it is a deliberate, one-at-a-time step we cover near the end.

One detail drives a couple of steps: the P1 editor registers your components. The set of blocks P1 lets editors place on a page is populated the first time someone opens /p1β€”the editor pushes your code's components to the repository using their login. Keep that in mind; it's why a newly added block "appears" in P1 only after someone opens the editor.

Before you begin

You'll need πŸ”§

  • Terminus β‰₯ 4.2.0, signed in (terminus auth:login picks up your saved token automatically).
  • A Pantheon Front-End Site connected to your GitHub repo.

Gather these values. Create your P1 site in the dashboard first, then collect the rest. Notice how many a non-technical teammate can grab on their own:

You'll need β†’ set an env var

Where to get it

Who

P1 site ID β†’ NEXT_PUBLIC_CSS_SITE_ID

Create the site at content.pantheon.io; the ID is in the dashboard URL.

🟒 Anyone

API token β†’ CSS_API_KEY

Dashboard β†’ generate a token, scope "All content including drafts."

🟒 Anyone

Base URL β†’ NEXT_PUBLIC_CSS_BASE_URL

Always https://ccr.p1.pantheon.io for production.

🟒 Anyone

Main branch ID β†’ NEXT_PUBLIC_CSS_BRANCH_ID

GET {BASE_URL}/api/sites/{siteId}/content-pages with header Authorization: Bearer <token> β†’ read branchId.

πŸ”§ Developer

Site machine name

terminus site:list

πŸ”§ Developer

Pin the branch ID. That content token is read-only β€” great for serving pages, but it can't list branches. If you leave NEXT_PUBLIC_CSS_BRANCH_ID empty, /p1 returns a 500. Always set it.


Choosing your version (an important heads-up)

The P1 SDK is distributed as a coordinated suite of packages that remain synchronized β€” css-client, puck-css, p1-next-sdk, alongside pds-toolkit-react. Install every package on a matching release. Right now, 0.14.0 is current, 0.15 sits in canary, and minor updates arrive regularly β€” target a unified release line rather than relying on a static version tag.

npm add @pantheon-systems/css-client@latest \
        @pantheon-systems/puck-css@latest \
        @pantheon-systems/p1-next-sdk@latest 

Installing and wiring up P1 πŸ”§

1. Install the SDK

Install the coordinated set at your chosen version:

npm add @pantheon-systems/css-client@0.8.0 \
        @pantheon-systems/puck-css@0.8.0 \
        @pantheon-systems/p1-next-sdk@0.8.0 

2. Bring in the starter routes

Copy these from pantheon-systems/puck-css-integration β†’ apps/p1-starter (the main branch), adapting the paths to your repo (for example, under src/). Keep the starter's folder structure and ensure its relative imports resolve cleanly.

  • app/p1/api/[...p1]/route.ts β†’ the API handler (createP1Handler)
  • app/p1/auth/[...action]/route.ts β†’ the auth handler (keep prompt: "select_account", not "login", which would ask people to sign in on every visit)
  • app/p1/(editor)/[[...p1]]/… β†’ the editor itself
  • app/[...puckPath]/{page.tsx,client.tsx} β†’ the catch-all that renders published P1 pages at real URLs. Heads-up: this changes what happens on unknown paths β€” instead of a hard 404, they show P1's "create this page" screen (a 200). Make sure that's what you want.
  • puck.config.tsx, the starter blocks in components/puck/*, and any datasource or SEO helpers those routes import.

Leave your own layout.tsx, page.tsx, and existing pages alone β€” don't let the starter's versions overwrite them.

3. Run the editor-layout migration

A small codemod moves the editor into a persistent layout so it doesn't fully reload as editors move between pages. It needs a clean git tree:


npx p1-migrate --dry-run   # checks: clean tree, nothing missing, not already migrated
npx p1-migrate             # makes the change

4. Turn on managed redirects

Add a middleware.ts that runs createP1Middleware. It checks each incoming path against P1 and applies redirects your team configures in the dashboard β€” no new secrets, it reuses what you already set. (On Next 16.3+ this file is being renamed to proxy; the current name still works.)

5. Adjust next.config

  • Add the three SDK packages to transpilePackages.
  • Add experimental.staleTimes.dynamic: 0 so client cache doesn't hold back fresh content.
  • Add a webpack alias that forces a single copy of yjs (yjs: require.resolve("yjs")). (This is what keeps the 0.8 build healthy β€” and, as noted above, what doesn't carry to a turbopack build.)
  • Keep your site's nav and footer off /p1. If your root layout wraps everything in global chrome, gate it behind a small client component that checks usePathname() and renders nothing on /p1*, so the editor opens on a clean surface.

6. Point at P1 locally and check

Create .env.local (git-ignored):

NEXT_PUBLIC_CSS_BASE_URL=https://ccr.p1.pantheon.io
NEXT_PUBLIC_CSS_SITE_ID=<site id>
NEXT_PUBLIC_CSS_BRANCH_ID=<main branch id>   # pinned β€” see the note above
CSS_API_KEY=<token>

Then confirm:

  • npm run build succeeds.
  • /p1 shows the sign-in screen, and signing in takes you into the editor.
  • POST /p1/auth/login returns 200 with a loginUrl. (A 500 means the base URL or token isn't reaching the runtime.)
  • Your existing pages still work and static assets load.

Going live πŸ”§

1. Set your Pantheon secrets

Set the build-time values site-wide (these get baked into the build, and per-environment overrides aren't read at build time):

This requires a Pantheon Dashboard SSH key - follow the steps here to create one if you don’t already have an SSH key.

terminus secret:site:set <site> NEXT_PUBLIC_CSS_BASE_URL "https://ccr.p1.pantheon.io" --type=env --scope=web -n
terminus secret:site:set <site> NEXT_PUBLIC_CSS_SITE_ID   "<id>"    --type=env --scope=web -n
terminus secret:site:set <site> NEXT_PUBLIC_CSS_BRANCH_ID "<uuid>"  --type=env --scope=web -n
terminus secret:site:set <site> CSS_API_KEY               "<token>" --type=env --scope=web -n

P1_SITE_URL keeps the sign-in redirect pointed at the right place behind Pantheon's proxy. It's per-environment β€” but an environment override needs a site-wide default to exist first:

terminus secret:site:set <site>      P1_SITE_URL "https://dev-<site>.pantheonsite.io"  --type=env --scope=web -n  # default
terminus secret:site:set <site>.live P1_SITE_URL "https://live-<site>.pantheonsite.io" --type=env --scope=web -n  # per-env

2. Deploy by pushing and tagging

This site type deploys by git reference (opening a PR only runs security scans):

  • Push main β†’ builds and deploys Dev.
  • Test and Live go out as tags: pantheon_test_N / pantheon_live_N (incrementing numbers). The first tag also creates that environment.

git tag pantheon_live_1 -a -m "Deploy to Live" && git push origin pantheon_live_1

  • Need a multidev? Create it with terminus multidev:create <site>.dev <name> β€” not by pushing a branch.

3. Clear the cache, then check β€” every time

After every deploy, clear the environment's cache. Pantheon's CDN can cache a missing-asset response from a brief restart window and keep serving it, which leaves the site looking unstyled with no interactivity:

terminus env:clear-cache <site>.<env>

Then check the deployed URL: pages return 200, /p1/auth/login returns 200, a stylesheet under /_next/static/ returns 200, and /p1 opens the editor. Build logs live at terminus node:logs:build:list <site>.<env>.

Editing your pages in P1

This is where your team lives day to day β€” and where P1 gets closest to fully self-serve. Bringing an existing page into P1 has two parts: making its building blocks available (a developer, once) and creating and reviewing the content (anyone, forever after).

Make your components available (developer, once per component) πŸ”§

  • Register the page's blocks in puck.config.tsx and deploy that change.
  • Have someone open /p1 and sign in β€” loading the editor syncs those blocks into P1 so editors can use them.

Create and review the page β€” the everyday flow 🟒

  • In /p1, open a Workstream so your changes stay separate from your live site.
  • Build or edit the page with your registered blocks, and preview it right there using the Workstream switcher.
  • When it's ready, merge the Workstream into main to publish β€” think of it as hitting "publish," with a real review step in front of it.

Serve it at the real URL (developer, once per page) πŸ”§ For a page you'd previously hand-coded, remove or repoint that route so the catch-all serves the P1 version instead. Because content is shared across environments but routes ship per-environment, you can switch Dev to the P1 page while Live keeps the old one until you're ready.

Seeding content in bulk (advanced): P1's automation tools can create and publish pages programmatically with your login's permissions β€” useful for moving a page's existing content over in one shot. The blocks still have to be registered first (open the editor once, as above).

When something's not right

What you see

What's going on, and the fix

/p1 returns 500

NEXT_PUBLIC_CSS_BRANCH_ID isn't set (the read-only token can't list branches). Pin the branch ID.

POST /p1/auth/login returns 500

The base URL or CSS_API_KEY isn't reaching the runtime β€” check your secrets. (A 500 right after a deploy is often just the server restarting; retry.)

Site looks unstyled after a deploy

The CDN cached a missing-asset response. Run terminus env:clear-cache <site>.<env>. (If you have output: 'standalone' in your config, remove it β€” it causes the same symptom.)

Editor won't load documents; log shows "Yjs was already imported"

yjs got bundled twice. The webpack alias fixes it on the 0.8 build; it's the open blocker on the 0.10 turbopack build (see Choosing your version).

npm ci fails on @fortawesome/pro-*

Only on the older 0.5.x line (fixed in 0.8). Add a package.json override aliasing the pro-* icons to the public free-* ones.

The editor rejects a block: "unknown component type"

The block isn't registered yet. Deploy the puck.config change and open /p1 once to sync it.

A "CORS" error in the editor console

Usually a backend 500 (like a brief "pool timeout") returned without CORS headers, which the browser reports as CORS. It's the backend, not your code β€” retry, and flag persistent cases to the P1 team.

Clean CI build fails on a package that works locally

Declare every package you import directly in package.json (a "phantom" dependency resolves locally but fails a clean npm ci). Verify with rm -rf node_modules && npm ci && next build.


Reference

Environment variables

Variable

Baked in at build?

What it's for

NEXT_PUBLIC_CSS_BASE_URL

Yes

Production content backend: https://ccr.p1.pantheon.io

NEXT_PUBLIC_CSS_SITE_ID

Yes

Your P1 site ID

NEXT_PUBLIC_CSS_BRANCH_ID

Yes

Main branch ID (pin it)

CSS_API_KEY

No (runtime)

Read-only token that serves content

P1_SITE_URL

No (runtime, per-env)

Keeps the sign-in redirect correct behind the proxy

NEXT_PUBLIC_LD_CLIENT_ID

Yes (optional)

Turns on the 0.10 AI chatbot; unset = hidden

Deploy at a glance

To…

Run

Deploy Dev

git push origin main

Deploy Test / Live

tag pantheon_test_N / pantheon_live_N, then push the tag

Create a multidev

terminus multidev:create <site>.dev <name>

Clear cache

terminus env:clear-cache <site>.<env>

Read build logs

terminus node:logs:build:list <site>.<env>

Road to self-serve

We want a content creator to add P1 with only light help from engineering. These are the πŸ”§ steps still in the way, roughly in the order worth solving them:

  • The build itself β€” installing the SDK, bringing in the starter routes, adjusting next.config, running the migration. This is the biggest barrier and the best candidate for a one-command scaffolding tool or a dashboard "Add P1" action.
  • Secrets and deploy β€” a dashboard button that sets the secrets and triggers a build would remove the terminal entirely.
  • The branch-ID lookup β€” surfacing it in the dashboard turns the whole "gather these values" table green.

Everything under Editing your pages in P1 after that one-time block registration is already green β€” the editor and Workstream flow is exactly where a marketer will feel at home.

Next.js Integration
Pantheon Hosting
Visual Editor
Developer Workflow