Skip to main content

Deployment (Vercel)

ropa2.0 is deployed as a Next.js application on Vercel with MongoDB Atlas as the database. Tenants are identified by subdomains (<shortName>.rat.gd). Each subdomain must resolve to the same Vercel deployment.

For the documentation sites (Docusaurus on Cloudflare Pages), see Docs Deployment (Cloudflare).


Vercel project

SettingValue
Teamgdpr-labs (Hobby plan)
Projectropa2-0
Project IDprj_AMQcMMleeyJH5HmSoBE8vvEePV5E
Team IDteam_FsNGjbnVJzdZrdiDKzRkojKU
FrameworkNext.js (auto-detected, Turbopack)
Node.js22.x
Production domainsrat.gd, *.rat.gd
Repositorygithub.com/GDPR-Labs/ropa2.0 (private, org-owned)

How deploys work: GitHub Actions, not Git integration

Vercel's native Git integration is not used. Since the repository moved into the GDPR-Labs GitHub organization (June 2026), the Git integration is unavailable: Vercel's Hobby plan does not support private repositories owned by a GitHub organization (Pro is required for that).

Instead, deploys run through the GitHub Actions workflow .github/workflows/vercel-deploy.yml:

TriggerResult
Push to mainProduction deployment (rat.gd, *.rat.gd repointed automatically)
Push to any other branchPreview deployment (unique *.vercel.app URL)
Push touching only non-app paths (docs/**, website/**, AddOns/**, tests, repo tooling — see paths-ignore in the workflow)No app deploy — docs are handled by Cloudflare Pages, add-ons (e.g. the GForms Apps Script) are deployed separately

The workflow runs the standard Vercel CI sequence:

vercel pull # fetch project settings + env vars for the target environment
vercel build # build on the GitHub runner (--prod on main)
vercel deploy --prebuilt # upload the build output, no remote build

Implementation notes:

  • VERCEL_ORG_ID and VERCEL_PROJECT_ID are hard-coded in the workflow env: block — they are identifiers, not secrets, and pinning them avoids a per-clone vercel link step.
  • Authentication uses the VERCEL_TOKEN repository secret (GitHub → repo → Settings → Secrets and variables → Actions). It is a Vercel personal token scoped to the gdpr-labs team, created at vercel.com/account/settings/tokens.
  • The Vercel CLI is pinned to a major version (vercel@54) in the workflow to avoid surprise breakage from @latest.
  • A concurrency group cancels superseded in-flight deploys of the same branch.
  • Because the build runs with vercel build, all environment variables are pulled from Vercel — nothing app-related is stored in GitHub secrets except the token.

Operational consequences

  • Build logs live in GitHub Actions (repo → Actions → "Vercel Deploy"), not in the Vercel dashboard. The Vercel dashboard still shows the resulting deployments, runtime logs, and domains.
  • If the VERCEL_TOKEN expires or is revoked, deploys fail in Actions (red ✗ on the commit). Fix: create a new token and run gh secret set VERCEL_TOKEN --repo GDPR-Labs/ropa2.0.
  • There are no automatic PR comments with preview URLs (that was a Git-integration feature). The preview URL is printed at the end of the workflow's Deploy step.
  • Rollback: in the Vercel dashboard, Deployments → … → Instant Rollback, or vercel rollback / vercel promote <deployment-url> with the CLI.

If the team ever upgrades to Vercel Pro, the native Git integration can be restored (Project → Settings → Git → Connect GDPR-Labs/ropa2.0) and the workflow deleted. The Vercel GitHub App is already installed on the GDPR-Labs organization.


Prerequisites

  • Node.js ≥ 20
  • A MongoDB Atlas cluster (M10 or higher recommended for production)
  • A Clerk application (Business plan for multi-org support)
  • A Cloudflare account with Turnstile enabled
  • A Vercel project (see table above)
  • A domain with wildcard DNS support

1. MongoDB

1.1 Create a cluster

Use MongoDB Atlas. Recommended: M10 (dedicated) for production; M0 (free shared) is fine for staging.

1.2 Create a database user

Database Access → Add New Database User — grant readWriteAnyDatabase on the target database.

1.3 Allowlist Vercel IPs

Network Access → Add IP Address → Allow Access from Anywhere (0.0.0.0/0).

Vercel serverless functions use dynamic egress IPs that cannot be statically allowlisted.

1.4 Get the connection string

Connect → Drivers → Node.js — copy the connection string:

mongodb+srv://<user>:<password>@<cluster>.mongodb.net/<dbname>?retryWrites=true&w=majority

Replace <dbname> with your target database name (e.g. ropa2).


2. Clerk

2.1 Create a Clerk application

  1. Sign in at https://clerk.com.
  2. Create a new application — enable Email and your required social providers.
  3. Switch to Organizations mode: Settings → Organizations → Enable Organizations.

2.2 Create the demo organization

The demo tenant is special — its clerkOrganizationId is hard-coded in src/lib/mongoose/organizationFromTemplateCreate.js (KNOWN_CLERK_ORG_IDS). Create a Clerk organization with slug demo and copy its ID into that constant before the first deploy.

2.3 Configure the webhook

Webhooks → Add Endpoint:

  • URL: https://<your-domain>/api/webhooks/clerk
  • Events to subscribe: organization.updated, organization.deleted

Copy the Signing Secret — this becomes CLERK_WEBHOOK_SIGNING_SECRET.

2.4 Collect keys

From API Keys:

VariableWhere in Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYPublishable key
CLERK_SECRET_KEYSecret key
CLERK_WEBHOOK_SIGNING_SECRETWebhook signing secret (step 2.3)

3. Cloudflare Turnstile

  1. Sign in at https://dash.cloudflare.com.
  2. Turnstile → Add site — domain: *.rat.gd (wildcard covers all tenants).
  3. Copy the Site Key and Secret Key.
VariableValue
NEXT_PUBLIC_TURNSTILE_SITE_KEYSite key
TURNSTILE_SECRET_KEYSecret key

4. Environment Variables

Set all of the following in Vercel → Settings → Environment Variables. Mark variables starting with NEXT_PUBLIC_ as available to all environments; server-only variables should be Production + Preview only.

The GitHub Actions workflow pulls these automatically at build time via vercel pull — they are not duplicated as GitHub secrets.

Required

VariableDescription
MONGODB_URIFull MongoDB Atlas connection string
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk frontend key
CLERK_SECRET_KEYClerk backend key
CLERK_WEBHOOK_SIGNING_SECRETSvix signature secret for webhook verification
NEXT_PUBLIC_SITE_DOMAINRoot domain without protocol (e.g. rat.gd)
NEXT_PUBLIC_TURNSTILE_SITE_KEYCloudflare Turnstile site key
TURNSTILE_SECRET_KEYCloudflare Turnstile server-side secret

Optional — logging & telemetry

VariableDefaultDescription
LOG_LEVELdebug (dev), info (prod)Pino log level
NEXT_PUBLIC_SENTRY_DSNSentry DSN for browser error tracking
SENTRY_DSNSentry DSN for server error tracking
SENTRY_ORGSentry org slug (for source map upload at build time)
SENTRY_PROJECTSentry project slug
SENTRY_AUTH_TOKENSentry auth token for source map upload
AXIOM_TOKENAxiom ingest token (activates Pino → Axiom transport)
AXIOM_DATASETAxiom dataset name (e.g. ropa2-production)

See telemetry.md for full Sentry + Axiom setup.

Optional — build

VariableDefaultDescription
ANALYZEfalseSet to true to generate bundle analysis report
CISet by the CI runner automatically; suppresses Sentry build logs when absent

5. DNS

All tenant subdomains route to the same Vercel deployment. Two DNS records are needed:

TypeNameValue
A / CNAME@Vercel IP / cname.vercel-dns.com
CNAME*cname.vercel-dns.com

The wildcard record catches all <shortName>.rat.gd subdomains. In Vercel → Project Settings → Domains, add:

  • rat.gd
  • *.rat.gd

The rat.gd DNS zone is managed by Vercel DNS. This is why the dev docs site cannot sit behind Cloudflare Access on a rat.gd subdomain — see Docs Deployment (Cloudflare).


6. Deploying

Push to main. The GitHub Actions workflow (see above) builds and deploys to production; pushes to any other branch produce preview deployments.

On the first successful deploy, run the demo data seed (step 7).


7. Seed Demo Data

The demo tenant (demo.rat.gd) is created via the admin route:

POST /api/admin/recreate-demo

This is a protected endpoint. Trigger it once after the first deploy to create the demo organization, its ROPAs, and link it to the Clerk org created in step 2.2.


8. Post-Deployment Verification

Check each item after deploying:

  • The "Vercel Deploy" workflow run is green in GitHub Actions
  • The new deployment shows READY / Production in the Vercel dashboard
  • https://demo.<your-domain> loads without error
  • Clerk sign-in works and redirects back to the subdomain
  • Cloudflare Turnstile challenge appears on protected forms
  • Creating a new ROPA activity saves to MongoDB
  • PDF export generates and downloads correctly
  • Clerk webhook fires on org update (check server logs)
  • Sentry receives a test error (see telemetry.md#verification)
  • Axiom receives structured Pino logs (see telemetry.md#axiom)

9. Development Setup

# Install dependencies
npm install

# Copy env files
cp .env.e2e.example .env.e2e # fill in E2E_CLERK_USER_EMAIL

# Create .env.local with the variables from section 4
# at minimum: MONGODB_URI, CLERK keys, TURNSTILE keys, NEXT_PUBLIC_SITE_DOMAIN

# Run dev server
npm run dev

Local subdomains: demo.localhost:3000, upf.localhost:3000.

For local subdomain routing to work, add entries to /etc/hosts:

127.0.0.1 demo.localhost
127.0.0.1 upf.localhost

Useful scripts

CommandDescription
npm run devStart dev server
npm run buildProduction build
npm testUnit + service tests (Vitest)
npm run test:e2ePlaywright E2E tests
npm run lintESLint
npm run analyzeBundle size analysis (ANALYZE=true)

10. Multi-Environment Strategy

EnvironmentBranchDomainNotes
Productionmain*.rat.gdLive data; all env vars required
PreviewFeature branchesVercel preview URLsUse staging Atlas cluster; Clerk test mode
DevelopmentLocal*.localhost:3000.env.local; mongodb-memory-server optional

Clerk has separate Development and Production instances — use the development instance keys for local and preview environments.