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.
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
By the end, your site has:
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.
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.
You'll need π§
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.
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 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 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.
Leave your own layout.tsx, page.tsx, and existing pages alone β don't let the starter's versions overwrite them.
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 changeAdd 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.)
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:
Going live π§
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-envThis site type deploys by git reference (opening a PR only runs security scans):
git tag pantheon_live_1 -a -m "Deploy to Live" && git push origin pantheon_live_1After 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>.
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) π§
Create and review the page β the everyday flow π’
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).
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. |
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> |
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:
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.