Skip to main content

Prerequisites

Before deploying, ensure the following are in place:
  • Vercel account with a Team Plan (Pro or Enterprise) — required for environment variable scoping, deployment protection, and team audit logs
  • GitHub repository connected to the Vercel team
  • Neon PostgreSQL database provisioned (separate databases for staging and production)
  • Cloudflare account with pensionportal.ai domain configured

Initial Vercel Setup

Follow these steps to configure the Vercel project from scratch:
  1. Connect GitHub repository — In the Vercel Dashboard, click “Add New Project” and import the IORP-II-App repository from GitHub.
  2. Set Framework Preset — Vercel auto-detects Next.js. Confirm the preset is set to Next.js.
  3. Set Build Commandnpm run build
  4. Set Output Directory.next (auto-configured by Vercel for Next.js)
  5. Set Install Commandnpm install
  6. Set Node.js Version — Select 20.x in Project Settings → General → Node.js Version.

Domain Mapping

DeploymentDomainNotes
Productionapp.pensionportal.aiCustom domain added in Vercel Dashboard
Marketingwww.pensionportal.aiSeparate Vercel project or same project with multiple domains
PR Previews*.vercel.app (auto-generated)No custom domain needed for preview deployments
To add the custom domain:
  1. Go to Vercel Dashboard → Your Project → SettingsDomains
  2. Add app.pensionportal.ai
  3. Vercel will display a CNAME record to add in Cloudflare DNS
  4. In Cloudflare, add the CNAME pointing to cname.vercel-dns.com with Proxy status: Proxied
See the Cloudflare Configuration Guide for the full DNS record table.

Environment Variable Matrix

Configure environment variables in Vercel Dashboard → Project → SettingsEnvironment Variables. Set each variable for the correct environment scope (Development, Preview, Production).
VariableDevelopmentStaging (Preview)Production
DATABASE_URLpostgresql://localhost:5432/iorp_devpostgresql://neon-staging.../iorp_stagingpostgresql://neon-prod.../iorp_prod
DATABASE_URL_UNPOOLEDSame as DATABASE_URLNeon staging unpooled URLNeon prod unpooled URL
AUTH_SECRETAny 32+ character stringGenerated secret (Vercel encrypted)Generated secret (Vercel encrypted, different from staging)
AUTH_URLhttp://localhost:3000https://iorp-staging.vercel.apphttps://app.pensionportal.ai
ANTHROPIC_API_KEYsk-ant-... (dev key)sk-ant-... (staging key)sk-ant-... (prod key, rate-limited)
NEXT_PUBLIC_APP_URLhttp://localhost:3000https://iorp-staging.vercel.apphttps://app.pensionportal.ai
PPS_ENCRYPTION_KEY64-char hex (dev only, never real data)Staging hex key (Vercel encrypted)Prod hex key (Vercel encrypted, NEVER same as staging)
RESEND_API_KEYOptionalre_staging_...re_prod_...
ADMIN_EMAILdev@localstaging-admin@pensionportal.aiadmin@pensionportal.ai
EMAIL_FROMnoreply@localnoreply@staging.pensionportal.ainoreply@pensionportal.ai
NODE_ENVdevelopment (auto)production (auto)production (auto)
PPS_ENCRYPTION_KEY must be unique per environment. A staging key used in production would mean that data encrypted in staging could be decrypted in production and vice versa — a critical GDPR and IORP II violation. Rotate immediately if a key is ever shared across environments.
AUTH_SECRET must be unique per environment. Sharing secrets across environments allows session tokens from one environment to be valid in another.
Production secrets must never appear in .env files committed to the repository. All sensitive values must be set exclusively through the Vercel Dashboard with appropriate environment scoping.

Generating Secrets

Generate AUTH_SECRET:
openssl rand -hex 32
Generate PPS_ENCRYPTION_KEY:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

CI Enforcement on Vercel

Vercel automatically runs the following checks during each deployment build. All must pass before the deployment is promoted:
  1. npm run lint — ESLint must report zero errors
  2. TypeScript compilation — Run as part of the Next.js build step; type errors fail the build
  3. npm run build — Full Next.js production build must complete without errors
Add the following required status checks in GitHub Branch Protection Rules so that Vercel deployments are gated on CI results:
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, staging]
  pull_request:

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm install
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm install
      - run: npm test

  docs-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm install
      - run: npm run docs:check
Configure these jobs as required status checks in GitHub → Repository → Settings → Branches → Branch protection rules for main.

Rollback Procedure

Use this procedure when a production deployment introduces a regression:
  1. Go to Vercel Dashboard → Your Project → Deployments
  2. Identify the last known-good deployment (check timestamps and commit messages)
  3. Click the ”…” menu next to that deployment → Promote to Production
  4. Verify https://app.pensionportal.ai is serving the correct version (check the deployment SHA in response headers or the health endpoint)
  5. If a database migration was included in the bad deployment: run the rollback migration against production:
DATABASE_URL=<prod-url> npm run db:migrate:rollback
  1. Alert the team in Slack #ops with:
    • Incident start time
    • Affected deployment SHA
    • Rollback deployment SHA
    • Any data impact assessment
If the rollback involves a destructive schema change (e.g., column drop), coordinate with the engineering lead before running db:migrate:rollback. Irreversible migrations may require a point-in-time restore from Neon. See the Backup & Restore Runbook.

Preview Deployments

Vercel automatically creates a preview deployment for every pull request.
  • Preview URLs follow the pattern: https://iorp-ii-app-<hash>.vercel.app
  • Preview deployments use Preview scoped environment variables (staging values)
  • Share preview URLs with stakeholders for review before merging
Do NOT set PPS_ENCRYPTION_KEY to the production value in Preview environment variables. Preview deployments should use a dedicated preview/staging key. Real beneficiary PPS data must never be present in preview databases.
Database migrations must not run automatically on preview deployments. The npm run db:migrate command should only be executed manually against staging or production URLs, never as part of the Vercel build step.

Production Health Checks

The application exposes the following health endpoints. Configure an external uptime monitor (e.g., BetterUptime, UptimeRobot, or Vercel Monitoring) to poll these on a schedule.
EndpointExpected ResponseCheck Interval
GET https://app.pensionportal.ai/api/health/live200 {"status":"ok"}Every 60 seconds
GET https://app.pensionportal.ai/api/health/ready200 {"status":"ready"}Every 60 seconds
GET https://app.pensionportal.ai/api/health/db200 {"status":"ok","latencyMs":N}Every 60 seconds
Example: BetterUptime monitor configuration
URL: https://app.pensionportal.ai/api/health/live
Method: GET
Check interval: 60 seconds
Alert threshold: 2 consecutive failures
Alert channels: PagerDuty, Slack #ops
If latencyMs on the /api/health/db endpoint consistently exceeds 500ms, investigate Neon connection pooling configuration and Vercel region alignment.

Database Migration Process

Schema changes must be applied to staging before production. Never run migrations directly against production without staging validation.
  1. Run migration against staging first:
DATABASE_URL=<staging-url> npm run db:migrate
  1. Validate staging — smoke test key flows (login, scheme creation, compliance check) against the staging URL.
  2. For significant schema changes, consider maintenance mode — place production in a read-only or maintenance state to prevent writes during migration. For additive-only migrations (new columns with defaults, new tables), maintenance mode is optional.
  3. Run migration against production:
DATABASE_URL=<prod-url> npm run db:migrate
  1. Deploy the new application code via Vercel (merge to main or trigger a manual promotion).
Neon supports branching — consider creating a Neon branch from production to test migrations against a copy of real schema before applying to production. See Neon Branching for details.