CI/CD & Deployment
CI/CD
The CI/CD pipeline is built on GitHub Actions for automation (lint, type-check, test, deploy) and Vercel for hosting and deployment. The main branch maps to the production environment.
1. Repository
| Attribute | Value |
|---|---|
| Platform | GitHub |
| Default branch | main (production) |
| Development branch | dev (preview) |
| PR target | dev โ PR review โ merge to main |
| Access | Private repository |
Branch protection rules
main: Require PR review, require status checks to pass (lint, type-check, build), no direct push from sandboxdev: Require status checks to pass, direct push allowed from local dev
2. GitHub Actions Workflows
ci.yml โ Pull Request checks
Runs on every PR targeting dev or main. Must pass before merge is allowed.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main, dev]
jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check # runs tsc --noEmit
build:
runs-on: ubuntu-latest
needs: lint-and-typecheck
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Build
run: npm run build
deploy-preview.yml โ Preview deployment
Runs on push to dev. Deploys to a Vercel preview environment.
# .github/workflows/deploy-preview.yml
name: Deploy Preview
on:
push:
branches: [dev]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy to Vercel (preview)
run: npx vercel deploy --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
deploy-production.yml โ Production deployment
Runs on push to main. Deploys to Vercel production.
# .github/workflows/deploy-production.yml
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Deploy to Vercel (production)
run: npx vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
e2e.yml โ End-to-End tests
Runs on PRs to main. Uses Playwright against the preview deployment URL.
# .github/workflows/e2e.yml
name: E2E Tests
on:
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: npx playwright test
env:
BASE_URL: ${{ steps.deploy.outputs.preview_url }}
E2E_ADMIN_EMAIL: ${{ secrets.E2E_ADMIN_EMAIL }}
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
3. Vercel Integration
Vercel is connected to the GitHub repository via the Vercel GitHub App. This enables:
- Automatic preview deployments for every PR (unique URL per PR)
- Production deployment on merge to
main(via GitHub Actions--prodflag) - Build log streaming in the Vercel dashboard and in PR checks
Vercel project settings
| Setting | Value |
|---|---|
| Framework preset | Next.js |
| Build command | next build |
| Output directory | .next |
| Install command | npm ci |
| Node.js version | 20.x |
| Root directory | / (monorepo root) |
4. Environment Variables
Environment variables are set in the Vercel dashboard under Project โ Settings โ Environment Variables. They are never committed to the repository.
.env.local is in .gitignore. Never add SUPABASE_SERVICE_ROLE_KEY, STRIPE_SECRET_KEY, or RESEND_API_KEY to any file tracked by git.
For CI builds that need public variables (build step), secrets are duplicated as GitHub Actions secrets with the NEXT_PUBLIC_ prefix where required.
5. Build Command
next build
This compiles the Next.js application, runs static analysis for page/route errors, and produces the .next output directory. The build will fail if:
- TypeScript compilation errors exist (Next.js runs
tscduring build) - An API route or page is missing
export const runtime = 'edge'AND uses a Node.js-only API (caught at build time) - A dynamic import fails to resolve
6. Edge Runtime Enforcement
All route files and pages must export:
export const runtime = 'edge'
This is enforced at build time by Next.js (Node.js-incompatible APIs cause a build error) and by a custom ESLint rule:
// .eslintrc.js (custom rule excerpt)
// Warn if any file in app/api/** or app/**/page.tsx is missing the runtime export
Common edge-runtime incompatible APIs that cause build failures:
fsmodule (no filesystem in edge runtime)Buffer(useUint8Arrayinstead)process.cwd()(not available in edge)- Node.js
crypto(use Web Crypto API:crypto.subtle)
7. Type Checking
Type checking runs separately from the build using:
npm run type-check
# Equivalent to: tsc --noEmit
tsconfig.json is set to "strict": true. The most common type errors in this codebase relate to Supabase nested FK joins returning arrays (see Supabase Gotchas).
8. Linting
npm run lint
# Equivalent to: next lint
ESLint is configured via .eslintrc.js with:
- eslint-config-next (Next.js defaults)
- @typescript-eslint (TypeScript-specific rules)
- Custom rule for runtime = 'edge' enforcement
Linting failures block merge. Fix all errors before opening a PR.
9. FUSE-Safe Commit Pattern
When working in a FUSE-mounted workspace (e.g., Claude Code sandbox, certain CI environments), standard git add and git checkout commands cause file corruption. Use the git plumbing pattern described below. See Deployment for the full step-by-step.
Why FUSE causes problems:
- FUSE silently truncates large files on write operations
git addfrom a FUSE path can produce a truncated blob- The truncation affects both the on-disk file and the git object โ standard verification (
wc -lvsgit cat-file) will not catch it because both are truncated identically - The corrupted commit passes local checks but GitHub rejects the push with an object pack error
The safe pattern in brief:
- Write file content to
/tmp(not the FUSE mount) using Python - Hash the file from
/tmpusinggit hash-object -w --stdinwith Python subprocess - Verify blob line count:
git cat-file -p <sha> | wc -lmust matchwc -l /tmp/filename - Build trees using Python (never
printf | git mktreeโ causes duplicate entries) - Create commit using
git commit-treewith the last pushed SHA as parent - Update
refs/heads/mainvia Python file write - Push using explicit SHA:
git push origin <sha>:refs/heads/main
Full detail: Deployment โ FUSE-Safe Commit Procedure
10. Deployment Verification
After every production deployment, verify the following:
- [ ] Vercel deployment status shows "Ready" (not "Error" or "Building")
- [ ]
https://app.sonandigital.comloads with HTTP 200 - [ ] Admin login flow completes successfully
- [ ] One API route responds correctly (e.g.,
GET /api/admin/notifications) - [ ] Sentry receives a test event (check Sentry dashboard for recent activity)
- [ ] Cron jobs are listed in Vercel Dashboard โ Cron Jobs with correct schedules
- [ ] No new errors in Vercel function logs in the first 10 minutes post-deploy
See Deployment for the full checklist.
Deployment
This page covers everything needed to deploy the SONAN DIGITAL CRM to production: Vercel configuration, environment variables, the FUSE-safe commit procedure, rollback, and the post-deployment checklist.
1. Vercel Configuration
The project is deployed on Vercel. The GitHub integration triggers builds automatically on push to the configured branches.
| Setting | Value |
|---|---|
| Team | SONAN DIGITAL |
| Project name | sonan-digital-crm |
| Framework | Next.js |
| Region | Washington D.C. (iad1) โ closest to Supabase US East |
| Build command | next build |
| Output directory | .next (auto-detected) |
| Install command | npm ci |
| Node.js version | 20.x |
| Root directory | / |
Vercel project dashboard
Access via: vercel.com/sonan-digital/sonan-digital-crm
2. Edge Runtime Requirement
```typescript
export const runtime = 'edge'
```
Place this at the top of every `route.ts`, `page.tsx`, and `layout.tsx` file. Missing this on a route that uses Web-only APIs is caught at build time. Missing it on a route that accidentally uses a Node.js API may only fail at runtime in production.
**Vercel will not warn you during deployment if a route silently falls back to Node.js runtime.** Check the Functions tab in the Vercel dashboard after deployment to verify all functions are listed as Edge Functions.
3. Environment Variables
Set these in the Vercel dashboard under Project โ Settings โ Environment Variables. Apply to Production, Preview, and Development as appropriate.
| Variable | Environment | Description |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
All | Supabase project URL. Value: {{ NEXT_PUBLIC_SUPABASE_URL }} |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
All | Supabase anon/public key. Value: {{ NEXT_PUBLIC_SUPABASE_ANON_KEY }} |
SUPABASE_SERVICE_ROLE_KEY |
All | Supabase service role key (server-only). Value: {{ SUPABASE_SERVICE_ROLE_KEY }} |
RESEND_API_KEY |
All | Resend email API key. Value: {{ RESEND_API_KEY }} |
STRIPE_SECRET_KEY |
All | Stripe secret key. Value: {{ STRIPE_SECRET_KEY }} |
STRIPE_WEBHOOK_SECRET |
All | Stripe webhook signing secret. Value: {{ STRIPE_WEBHOOK_SECRET }} |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
All | Stripe publishable key (browser-safe). Value: {{ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY }} |
CRON_SECRET |
All | Shared secret for cron endpoint authentication. Value: {{ CRON_SECRET }} |
NEXT_PUBLIC_SENTRY_DSN |
All | Sentry DSN for client-side error reporting. Value: {{ NEXT_PUBLIC_SENTRY_DSN }} |
SENTRY_AUTH_TOKEN |
Production, Preview | Sentry auth token for source map uploads. Value: {{ SENTRY_AUTH_TOKEN }} |
NEXT_PUBLIC_APP_URL |
All | Public base URL. Production: https://app.sonandigital.com |
All variables listed above are already set in the Vercel dashboard. Do not add them again or ask team members to re-enter them unless a key has been rotated.
4. Domain Configuration
| Domain | Target | SSL |
|---|---|---|
app.sonandigital.com |
Vercel production deployment | Auto-managed by Vercel (Let's Encrypt) |
sonandigital.com |
Marketing site (separate project) | โ |
DNS is configured to point app.sonandigital.com to Vercel's nameservers. SSL certificates are auto-renewed by Vercel. No manual certificate management is required.
5. FUSE-Safe Commit Procedure
Standard git add, git checkout, and direct file writes to the FUSE mount path silently truncate large files. The truncation affects both the disk copy and the git blob simultaneously, so wc -l verification does not catch it. Use the procedure below without shortcuts.
Why this happens
FUSE (Filesystem in Userspace) overlays the real filesystem with a virtual layer. Large sequential writes to a FUSE-mounted path may be silently truncated at a buffer boundary. The truncation is not reported as an error by the OS, so write() returns success even though data was lost.
Step-by-step procedure
A. Write file content to /tmp (non-FUSE path) using Python
import subprocess
content = r"""
...full file content here...
"""
with open('/tmp/target-file.tsx', 'w') as f:
f.write(content)
print(f"Wrote {len(content)} characters to /tmp/target-file.tsx")
Never write directly to the FUSE-mounted workspace path. Always use /tmp.
B. Hash the file from /tmp using Python subprocess
with open('/tmp/target-file.tsx', 'rb') as f:
data = f.read()
result = subprocess.run(
['git', 'hash-object', '-w', '--stdin'],
input=data,
capture_output=True,
cwd='/path/to/fuse/repo'
)
blob_sha = result.stdout.decode().strip()
print(f"Blob SHA: {blob_sha}")
You may see: unable to unlink tmp_obj_*. This is a FUSE permission warning during hash-object. The blob IS written successfully. Verify with git cat-file -t <sha>.
C. Immediately verify blob line count (mandatory)
# Both numbers must match exactly
git cat-file -p <blob_sha> | wc -l
wc -l /tmp/target-file.tsx
If the numbers differ, the blob is truncated. Do not proceed. Re-hash before continuing.
D. Build trees using Python helpers
Never use printf | git mktree from shell โ it causes duplicate tree entries that GitHub rejects.
def ls_tree(treeish, repo_path):
result = subprocess.run(
['git', 'ls-tree', treeish],
capture_output=True, text=True, cwd=repo_path
)
entries = []
for line in result.stdout.strip().split('\n'):
if not line.strip():
continue
meta, name = line.split('\t', 1)
mode, typ, sha = meta.split()
entries.append((mode, typ, sha, name))
return entries
def mktree(entries, repo_path):
# Always sort before mktree
sorted_entries = sorted(entries, key=lambda x: x[3])
lines = '\n'.join(
f"{mode} {typ} {sha}\t{name}"
for mode, typ, sha, name in sorted_entries
)
result = subprocess.run(
['git', 'mktree', '--missing'],
input=lines + '\n',
capture_output=True, text=True, cwd=repo_path
)
return result.stdout.strip()
# Update a file in a subdirectory tree
# 1. Get parent directory tree
parent_entries = ls_tree('HEAD:src/components/admin', repo_path)
# 2. Remove old entry for the file being replaced
parent_entries = [(m, t, s, n) for m, t, s, n in parent_entries if n != 'TargetFile.tsx']
# 3. Append new blob
parent_entries.append(('100644', 'blob', blob_sha, 'TargetFile.tsx'))
# 4. Build the new subtree
new_subtree_sha = mktree(parent_entries, repo_path)
E. Verify no duplicate entries in every tree
count=$(git ls-tree <tree_sha> | wc -l)
unique=$(git ls-tree <tree_sha> | awk '{print $4}' | sort | uniq | wc -l)
echo "Entries: $count, Unique: $unique"
# Must be equal
F. Build the full root tree (walking up the directory hierarchy)
Repeat the ls_tree โ filter โ append โ mktree pattern for each parent directory up to the root. Each level replaces the child entry with the newly created subtree SHA.
G. Create the commit
# Parent MUST be the last successfully pushed SHA โ hardcode it, never assume HEAD
PARENT_SHA = "abc123..." # last pushed commit
result = subprocess.run(
['git', 'commit-tree', root_tree_sha, '-p', PARENT_SHA, '-m', 'feat: description'],
capture_output=True, text=True, cwd=repo_path,
env={**os.environ, 'GIT_AUTHOR_NAME': 'Name', 'GIT_AUTHOR_EMAIL': 'email',
'GIT_COMMITTER_NAME': 'Name', 'GIT_COMMITTER_EMAIL': 'email'}
)
new_commit_sha = result.stdout.strip()
print(f"New commit: {new_commit_sha}")
If a previous attempt produced a commit with a bad tree, using it as the parent pulls bad objects into the push pack. GitHub will reject the push. Always use the last successfully pushed SHA.
H. Update the ref
with open('/path/to/repo/.git/refs/heads/main', 'w') as f:
f.write(new_commit_sha + '\n')
I. Verify the push pack is clean
git rev-list --objects <new_commit_sha> ^<last_pushed_sha> | grep <any_suspected_bad_sha>
# Must return nothing (empty output = clean pack)
J. Push (from Windows terminal)
# Run from Windows PowerShell โ sandbox gets 403
git push origin <new_commit_sha>:refs/heads/main
6. Rollback
To revert to a previous deployment:
Via Vercel dashboard: 1. Go to Project โ Deployments 2. Find the last working deployment 3. Click the three-dot menu โ Promote to Production 4. Confirm. Traffic switches within seconds (no rebuild required)
Via git (if code fix is needed):
1. Identify the last good commit SHA
2. Create a revert commit: git revert <bad_commit_sha>
3. Push to main โ triggers a new production build
7. Preview Deployments
Every PR automatically gets a preview URL from Vercel:
https://sonan-digital-crm-<git-hash>.vercel.app
Preview deployments: - Use the same environment variables as production (Vercel copies them) - Are accessible only to team members with Vercel access (not public by default) - Are automatically cleaned up after the PR is closed
8. Production Promotion
If a branch is deployed as a preview and needs to be promoted:
- Merge the branch to
mainvia a PR - GitHub Actions
deploy-production.ymltriggers automatically - Vercel rebuilds from
mainand sets the new deployment as production
Or, if the preview deployment is already verified: 1. Vercel Dashboard โ Deployments โ find the preview โ Promote to Production
9. Vercel Cron Jobs
Cron jobs are configured in vercel.json:
{
"crons": [
{
"path": "/api/admin/invoices/cron",
"schedule": "0 8 * * *"
},
{
"path": "/api/admin/appointments/cron",
"schedule": "0 * * * *"
}
]
}
Vercel Cron requires a Pro plan. Cron jobs appear under Project โ Cron Jobs in the dashboard. Logs for each run appear in the Vercel function logs for that endpoint.
10. Post-Deployment Checklist
Run this checklist after every production deployment:
- [ ] Vercel dashboard shows deployment status "Ready"
- [ ]
https://app.sonandigital.comreturns HTTP 200 - [ ] Admin login at
/auth/logincompletes successfully (email + MFA) - [ ] MFA challenge works correctly
- [ ] Portal login at
/portal/logincompletes successfully - [ ]
GET /api/admin/notificationsreturns 200 (not 500) - [ ] Check Vercel โ Functions tab: all functions listed as Edge (not Lambda)
- [ ] Check Sentry dashboard: no new crash events in the last 15 minutes
- [ ] Check Vercel โ Cron Jobs: crons are listed with correct schedules
- [ ] Stripe webhook: verify Stripe dashboard shows recent successful webhook delivery
- [ ] If this release included DB migrations: verify migration applied by checking table structure in Supabase Dashboard
Monitoring
This page covers error tracking, log management, cron job verification, uptime monitoring, and alerting for the SONAN DIGITAL CRM production environment.
1. Sentry โ Error Tracking
Sentry is the primary error tracking and performance monitoring tool. It captures unhandled exceptions and performance traces from both the client-side (browser) and server-side (edge functions).
Configuration
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SENTRY_DSN |
Client-side SDK initialization (visible in browser โ safe) |
SENTRY_AUTH_TOKEN |
CI/CD source map upload (org token with org:ci scope โ server only) |
SDK initialization files
// sentry.client.config.ts โ runs in the browser
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.1, // 10% of transactions traced
replaysSessionSampleRate: 0.05, // 5% of sessions replayed
replaysOnErrorSampleRate: 1.0, // 100% of error sessions replayed
environment: process.env.NODE_ENV,
})
// sentry.server.config.ts โ runs in edge functions
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.05, // 5% of server transactions traced
environment: process.env.NODE_ENV,
})
Source map uploads
Source maps are uploaded to Sentry during the CI build step, enabling readable stack traces in production (which runs minified/compiled code). This requires SENTRY_AUTH_TOKEN with the org:ci scope.
# Automatically runs via next build with Sentry webpack plugin
# Configured in next.config.ts via withSentryConfig()
What Sentry captures
- Unhandled JavaScript exceptions (client and server)
- Failed API route handler errors (uncaught exceptions bubble to Sentry's Next.js instrumentation)
- React component render errors (via Error Boundaries instrumented by Sentry)
- Performance traces for page loads and API responses
What Sentry does NOT capture automatically
- Handled errors returned as
{ error: "..." }JSON responses โ these are expected application behavior - Supabase query errors when the application explicitly handles them
- Stripe webhook events (handled externally; check Stripe dashboard)
To manually capture a handled error for visibility:
import * as Sentry from '@sentry/nextjs'
try {
// risky operation
} catch (err) {
Sentry.captureException(err, {
tags: { feature: 'invoice-cron' },
extra: { tenantId: caller.tenantId },
})
// still return a user-facing error response
return NextResponse.json({ error: 'Cron failed' }, { status: 500 })
}
2. Vercel Logs
Vercel provides two types of logs accessible from the dashboard:
Build logs
Available at: Project โ Deployments โ [deployment] โ Build Logs
Use build logs to diagnose:
- TypeScript compilation errors
- Missing export const runtime = 'edge' on routes
- Missing environment variables during build
- next build output (page sizes, edge function sizes)
Function logs (runtime logs)
Available at: Project โ Logs (real-time streaming) or Project โ Deployments โ [deployment] โ Function Logs
Function logs capture:
- console.log / console.error output from edge functions
- Request metadata (path, status code, duration, region)
- Cold start events
Important logging conventions:
// Good โ structured logging with context
console.log(JSON.stringify({
event: 'invoice_cron_run',
processed: count,
overdue: overdueIds,
tenantId: 'redacted', // never log actual tenant IDs in production
durationMs: Date.now() - start,
}))
// Bad โ log sensitive data
console.log('Processing tenant', caller.tenantId, 'user', caller.userId)
Log retention
| Log type | Retention (free/hobby tier) | Retention (Pro tier) |
|---|---|---|
| Build logs | 7 days | 30 days |
| Function logs | 1 day | 7 days |
| Deployment logs | Indefinite (per deployment) | Indefinite |
The 1-day function log retention on the free tier means you must check logs promptly after an incident. Consider upgrading to Pro for 7-day retention or exporting logs to an external service (e.g., Datadog, Logtail).
3. Cron Job Verification
Cron jobs run on Vercel's scheduler and are visible in Project โ Cron Jobs in the dashboard.
Verifying a cron run completed
- Vercel Cron Jobs tab: Shows last run time and status (success/fail) for each configured cron.
- Vercel Function Logs: Filter by path (e.g.,
/api/admin/invoices/cron) to see the run's log output and response code. - Database verification: After the overdue invoice cron, query the DB to confirm expected changes:
-- Verify overdue cron ran: check invoices that should have been updated
SELECT id, status, due_date, updated_at
FROM invoices
WHERE due_date < CURRENT_DATE
AND status IN ('overdue', 'sent')
ORDER BY due_date ASC
LIMIT 20;
Manual cron trigger
To trigger a cron job manually (e.g., for testing or recovery after a missed run):
curl -X POST https://app.sonandigital.com/api/admin/invoices/cron \
-H "Authorization: Bearer {{ CRON_SECRET }}" \
-H "Content-Type: application/json"
Expected response:
{ "ok": true, "processed": 3 }
4. UptimeRobot (Recommended)
UptimeRobot provides external uptime monitoring โ it checks from outside Vercel's network and alerts on downtime even if Vercel's own dashboard shows the deployment as "Ready."
Recommended monitors
| Monitor name | URL | Type | Interval | Alert |
|---|---|---|---|---|
| CRM App | https://app.sonandigital.com |
HTTP(S) | 5 min | Email + SMS |
| Admin API Health | https://app.sonandigital.com/api/health |
HTTP(S) | 5 min | |
| Portal Health | https://app.sonandigital.com/portal |
HTTP(S) | 5 min |
Setup
- Register at uptimerobot.com
- Add monitors for the URLs above
- Set alert contacts to the engineering on-call email
- Configure keyword monitoring on
/api/healthto check for"ok":truein the response body
5. Health Endpoint (Recommended)
Implementing a /api/health endpoint is recommended for the v1.1 milestone. The spec below is the target design.
A /api/health endpoint should return the operational status of the application and its dependencies:
// app/api/health/route.ts
export const runtime = 'edge'
export async function GET() {
const checks: Record<string, 'ok' | 'error'> = {}
// Check Supabase DB connectivity
try {
const supabase = createServiceClient()
const { error } = await supabase.from('tenants').select('id').limit(1)
checks.database = error ? 'error' : 'ok'
} catch {
checks.database = 'error'
}
const allOk = Object.values(checks).every(v => v === 'ok')
return NextResponse.json({
ok: allOk,
version: process.env.NEXT_PUBLIC_APP_VERSION ?? 'unknown',
checks,
timestamp: new Date().toISOString(),
}, {
status: allOk ? 200 : 503,
})
}
Expected response when healthy:
{
"ok": true,
"version": "1.2.0",
"checks": {
"database": "ok"
},
"timestamp": "2025-03-15T10:00:00.000Z"
}
6. Alerting
Sentry alert rules
Configure in Sentry โ Project โ Alerts:
| Rule | Condition | Action |
|---|---|---|
| New issue | Any new unresolved issue | Email to engineering team |
| Error spike | Error rate > 10 errors/minute | Email + Slack notification |
| Performance regression | p95 latency > 3000ms | Email to engineering team |
| Unhandled crash | level:fatal event |
Page on-call engineer immediately |
Vercel error alerts
Vercel Pro tier provides email alerts for: - Build failures - Function error rate exceeding threshold
Configure at: Project โ Settings โ Notifications
Recommended alert escalation path
- Sentry / Vercel alert fires โ Email to
engineering@sonandigital.com - If unacknowledged after 15 minutes โ SMS to on-call engineer
- If unresolved after 30 minutes โ Escalate to team lead
7. Log Retention Summary
| Source | What it captures | Retention |
|---|---|---|
| Sentry | Exceptions, performance traces, replays | 90 days (free tier) |
| Vercel Build Logs | Compilation output, build errors | 7โ30 days |
| Vercel Function Logs | Runtime console output, request logs | 1โ7 days |
| Supabase Logs | DB query logs, auth events | 7 days (free), 30 days (Pro) |
| Stripe Dashboard | Webhook events, payment events | 30 days |
For compliance or long-term debugging, consider integrating Vercel log drains to ship function logs to a persistent store such as Datadog, Logtail, or AWS CloudWatch. This is recommended before the platform onboards regulated clients.