> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pensionsportal.ie/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel Deployment Guide

> Complete guide to deploying PensionsPortal.ie on Vercel

## 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 `pensionsportal.ie` 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 Command** — `npm run build`
4. **Set Output Directory** — `.next` (auto-configured by Vercel for Next.js)
5. **Set Install Command** — `npm install`
6. **Set Node.js Version** — Select **20.x** in Project Settings → General → Node.js Version.

***

## Domain Mapping

| Deployment  | Domain                          | Notes                                                         |
| ----------- | ------------------------------- | ------------------------------------------------------------- |
| Production  | `app.pensionsportal.ie`         | Custom domain added in Vercel Dashboard                       |
| Marketing   | `www.pensionsportal.ie`         | Separate 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 → **Settings** → **Domains**
2. Add `app.pensionsportal.ie`
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](/deployment/cloudflare) for the full DNS record table.

***

## Environment Variable Matrix

Configure environment variables in Vercel Dashboard → Project → **Settings** → **Environment Variables**. Set each variable for the correct environment scope (Development, Preview, Production).

| Variable                | Development                             | Staging (Preview)                           | Production                                                      |
| ----------------------- | --------------------------------------- | ------------------------------------------- | --------------------------------------------------------------- |
| `DATABASE_URL`          | `postgresql://localhost:5432/iorp_dev`  | `postgresql://neon-staging.../iorp_staging` | `postgresql://neon-prod.../iorp_prod`                           |
| `DATABASE_URL_UNPOOLED` | Same as `DATABASE_URL`                  | Neon staging unpooled URL                   | Neon prod unpooled URL                                          |
| `AUTH_SECRET`           | Any 32+ character string                | Generated secret (Vercel encrypted)         | Generated secret (Vercel encrypted, **different from staging**) |
| `AUTH_URL`              | `http://localhost:3000`                 | `https://iorp-staging.vercel.app`           | `https://app.pensionsportal.ie`                                 |
| `ANTHROPIC_API_KEY`     | `sk-ant-...` (dev key)                  | `sk-ant-...` (staging key)                  | `sk-ant-...` (prod key, rate-limited)                           |
| `NEXT_PUBLIC_APP_URL`   | `http://localhost:3000`                 | `https://iorp-staging.vercel.app`           | `https://app.pensionsportal.ie`                                 |
| `PPS_ENCRYPTION_KEY`    | 64-char hex (dev only, never real data) | Staging hex key (Vercel encrypted)          | Prod hex key (Vercel encrypted, **NEVER same as staging**)      |
| `RESEND_API_KEY`        | Optional                                | `re_staging_...`                            | `re_prod_...`                                                   |
| `ADMIN_EMAIL`           | `dev@local`                             | `staging-admin@pensionsportal.ie`           | `admin@pensionsportal.ie`                                       |
| `EMAIL_FROM`            | `noreply@local`                         | `noreply@staging.pensionsportal.ie`         | `noreply@pensionsportal.ie`                                     |
| `NODE_ENV`              | `development` (auto)                    | `production` (auto)                         | `production` (auto)                                             |

<Warning>
  **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.
</Warning>

<Warning>
  **AUTH\_SECRET must be unique per environment.** Sharing secrets across environments allows session tokens from one environment to be valid in another.
</Warning>

<Note>
  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.
</Note>

### Generating Secrets

Generate `AUTH_SECRET`:

```bash theme={null}
openssl rand -hex 32
```

Generate `PPS_ENCRYPTION_KEY`:

```bash theme={null}
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

### Recommended GitHub Actions Integration

Add the following required status checks in GitHub Branch Protection Rules so that Vercel deployments are gated on CI results:

```yaml theme={null}
# .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.pensionsportal.ie` 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:

```bash theme={null}
DATABASE_URL=<prod-url> npm run db:migrate:rollback
```

6. Alert the team in **Slack `#ops`** with:
   * Incident start time
   * Affected deployment SHA
   * Rollback deployment SHA
   * Any data impact assessment

<Warning>
  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](/runbooks/backup-restore).
</Warning>

***

## 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

<Warning>
  **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.
</Warning>

<Warning>
  **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.
</Warning>

***

## 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.

| Endpoint                                             | Expected Response                   | Check Interval   |
| ---------------------------------------------------- | ----------------------------------- | ---------------- |
| `GET https://app.pensionsportal.ie/api/health/live`  | `200 {"status":"ok"}`               | Every 60 seconds |
| `GET https://app.pensionsportal.ie/api/health/ready` | `200 {"status":"ready"}`            | Every 60 seconds |
| `GET https://app.pensionsportal.ie/api/health/db`    | `200 {"status":"ok","latencyMs":N}` | Every 60 seconds |

**Example: BetterUptime monitor configuration**

```
URL: https://app.pensionsportal.ie/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:**

```bash theme={null}
DATABASE_URL=<staging-url> npm run db:migrate
```

2. **Validate staging** — smoke test key flows (login, scheme creation, compliance check) against the staging URL.

3. **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.

4. **Run migration against production:**

```bash theme={null}
DATABASE_URL=<prod-url> npm run db:migrate
```

5. **Deploy the new application code** via Vercel (merge to `main` or trigger a manual promotion).

<Note>
  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](https://neon.tech/docs/introduction/branching) for details.
</Note>
