Adding pull request previews
A pull request preview is both applications built from one pull request, served at a URL of its own, with a database of its own. By the end of this page, every pull request gets one, every push to it redeploys it, and closing it deletes it.
flowchart LR
L["1. Local setup<br/>Both apps on your machine"] --> D["2. Deploying development<br/>A push deploys"]
L --> R["**3. Adding pull request previews**<br/>Independent of development"]
D --> S["4. Adding staging<br/>Optional"]
S --> P["5. Going to production<br/>Your domain, real users"]
D -. "without staging" .-> P
Previews stand on their own. Each pull request is a Cloudflare Worker Preview named pr-<n> on
preview's own Worker, <slug>-<app>-preview, bound to preview's own bucket and content database.
apps/web also gets a Preview Database: a Neon branch of preview's Neon project, migrated from empty
by the pull request's own run. Nothing in a preview belongs to the development context, so this
page works before
Deploying development, after it, or without it.
Pull request previews explains the mechanism; you
can finish this page without it.
Everything here starts on the development branch that
Local setup had you create.
PROJECT_SLUG, and the first pull request's run creates preview's Worker and resources.Prerequisites
| You need | How to get it | Required |
|---|---|---|
| Local setup finished | Local setup | Yes |
| The repository connected to Cloudflare | Connect the repository to Cloudflare. Already done if development deploys | Yes |
Your workers.dev subdomain | The Workers & Pages page, beside Your subdomain | Yes |
| A Neon account | Sign up for Neon. Pull request previews (apps/web) need Neon: each pull request branches preview's Neon project | Yes |
| A DNS zone you control | The value you set as PROJECT_ZONE. Add it to Cloudflare, or keep DNS access at your host | Only for outgoing email |
Polar credentials for preview | Set up sign-in and payments — a separate organization per context | Only for payments |
Add previews
1. Create preview's Neon project
Preview needs a Neon project of its own, never one another Runtime Context uses. Each pull request's
run branches its default branch into a Preview Database pr-<n>, and closing the pull request
deletes that branch. Create the project with the Neon CLI,
signed in as an admin of your Neon organization:
# Creates the project; note the ID it prints.
neon projects create --name <slug>-web-preview --no-secrets
# Creates a key that reaches this one project; it prints the key once.
neon api-keys create --name <slug>-web-preview-github-actions --project-id <project id>
In the Neon Console instead, create the project with New Project on your organization's projects page. Then open your organization's Settings → API keys, click Create new, and select Project-scoped for that project. Manage API keys is the fallback when a screen moves.
Neon shows the key once, and you store it in the next step. A lost key means creating another and revoking the first.
neondb database. Every
Preview Database starts as a copy of it, and the pull request's run migrates that copy from zero.
Never point another context at this project, and never migrate its default branch.2. Fill preview's secret folder
preview reads the development Secret Tier's /preview folder in apps/web's Infisical
project — the project id is the projectId committed in apps/web/.env.infisical. Store the Neon key
there as NEON_API_KEY:
infisical secrets set NEON_API_KEY='<the Neon key>' \
--env=development --path=/preview --projectId='<apps/web project id>'
Store the sign-in for the Platform Admin seeded into every Preview Database beside it:
infisical secrets set SEED_PLATFORM_ADMIN_EMAIL='<user>@<org>.com' \
SEED_PLATFORM_ADMIN_PASSWORD='<at least 8 characters>' \
--env=development --path=/preview --projectId='<apps/web project id>'
The email must be a real address you own. Polar rejects reserved domains such as example.com, and
password-reset mail goes to it. Every reviewer signs in with these two values.
Then add preview's own value for every key that
Set up your secret store put in
/local. A feature you're skipping takes a stand-in, as it does locally. Preview needs no
DATABASE_URL: each pull request's run reads its Preview Database's connection string from Neon.
3. Write preview's values
Each application's .env.preview ships with these values empty. The origins are your preview
Workers' own, built from your slug and the workers.dev subdomain:
NUXT_PUBLIC_APP_BASE_URL=https://<slug>-web-preview.<subdomain>.workers.dev
NUXT_PUBLIC_APP_WEBSITE_BASE_URL=https://<slug>-site-preview.<subdomain>.workers.dev
GOOGLE_CLIENT_ID=<preview's Google client ID>
GITHUB_CLIENT_ID=<preview's GitHub client ID>
NEON_PROJECT_ID=<the project ID from step 1>
NUXT_PUBLIC_APP_BASE_URL=https://<slug>-site-preview.<subdomain>.workers.dev
NUXT_PUBLIC_CONTACT_EMAIL=hello@example.com
NEON_PROJECT_ID is the project's ID, such as green-breeze-12345678, from the project's
Settings page. The br-… ID beside it names a branch, and the schema rejects it.
No pull request is served from these origins: the workflow puts pr-<n>- in front of each one
before the build, as Pull request previews
describes.
https://…workers.dev value — no quotes, no trailing comment. Anything else
ends the run red at Decide whether this run can deploy. NUXT_PUBLIC_APP_BASE_URL must also name
the Worker your slug builds, <slug>-<app>-preview, or Deploy the pull request preview ends red
because the preview's URL and the bundle's origin disagree.Social sign-in can't complete on a preview —
Register these callback URLs
says why — so Google and GitHub take stand-ins here at no cost, in both client IDs and in their
client secrets in /preview.
4. Open a pull request
Commit both files on a new branch off development, push it, and open a pull request into
development. Its first run creates preview's Worker, bucket and content database, each named
<slug>-<app>-preview, and every later pull request finds them. It also creates the pull request's
Preview Database, the Neon branch pr-<n>, behind the Hyperdrive configuration
<slug>-web-preview-pr-<n>, and migrates and seeds it before the upload.
The workflow comments on the pull request with each application's preview URL,
https://pr-<n>-<slug>-<app>-preview.<subdomain>.workers.dev. Every push to the pull request
redeploys that preview and migrates its Preview Database. Closing the pull request, merged or not,
deletes both.
Sign in to the apps/web preview as the seeded Platform Admin, with SEED_PLATFORM_ADMIN_EMAIL and
SEED_PLATFORM_ADMIN_PASSWORD from step 2. Seed data explains how to add rows to the seed.
5. Onboard preview's sending domains
Previews send from <slug>-<app>-preview.<zone>, one hostname per application, shared by every
pull request. Onboard each one the way
Email will not send until you onboard a domain
walks it, reading the hostname with APP_RUNTIME_CONTEXT=preview.
When it does not work
| What you see | What to fix |
|---|---|
| A green run and no preview comment | The deploy was skipped — When a deploy is skipped lists why |
Red at Decide whether this run can deploy, naming an origin | NUXT_PUBLIC_APP_BASE_URL in that application's .env.preview is empty or not a bare workers.dev origin |
Red at Decide whether this run can deploy, naming a variable | INFISICAL_GH_PRS_IDENTITY_ID is unset — The secret and the variables |
| A red run naming a key | That value is empty in apps/*/.env.preview or in the development tier's /preview folder |
The run stops at Upsert the Cloudflare resources | NEON_API_KEY or NEON_PROJECT_ID, or one of the token's resource permissions |
Upsert the Cloudflare resources fails creating a Neon branch or a Hyperdrive configuration | Neon's branch cap or Cloudflare's Hyperdrive cap is reached. Close a pull request you no longer need, then rerun the job |
wrangler printed no Preview URL | Preview URLs are off on the preview Worker. Turn them on under that Worker's Domains & Routes |
the preview serves … but the bundle was built for … | NUXT_PUBLIC_APP_BASE_URL in .env.preview names a Worker other than <slug>-<app>-preview |
| Google or GitHub sign-in fails on a preview | Expected on every preview |
Next steps
- Pull request previews — where a preview's logs are, what it binds, and how long it lives
- Deploying development — put
developmenton Cloudflare, if you haven't - Adding staging — optional, a pre-production context between
developmentand production
