Skip to content

Latest commit

 

History

History
1046 lines (764 loc) · 20.5 KB

File metadata and controls

1046 lines (764 loc) · 20.5 KB

Deployment Guide

Complete deployment documentation for JudgeFinder Platform

Version: 1.0.0 Last Updated: 2025-10-26 Status: Production

Table of Contents

  1. Build Process
  2. Environment Setup
  3. Netlify Deployment
  4. CI/CD Pipeline
  5. Database Migrations
  6. Monitoring
  7. Production Checklist
  8. Troubleshooting

Build Process

Development Build

# Start development server
npm run dev

# Development server with Turbo (faster)
npm run dev:turbo

Development Features:

  • Hot module replacement (HMR)
  • Source maps enabled
  • Error overlay in browser
  • No minification
  • Fast refresh for React components

Production Build

# Validate environment variables first
npm run validate:env

# Build for production
npm run build

# Test production build locally
npm run start

Build Optimization

Next.js automatically optimizes your build:

Static Assets:

  • CSS minification and bundling
  • JavaScript minification via SWC
  • Image optimization via next/image
  • Font optimization
  • Code splitting per route

Server Components:

  • React Server Components (RSC) for zero JS by default
  • Selective hydration for interactive components
  • Streaming SSR for faster TTFB

Bundle Analysis:

# Analyze bundle size
ANALYZE=true npm run build

This opens a visual bundle analyzer showing:

  • Size of each module
  • Which libraries are included
  • Opportunities for optimization

Environment Variables in Build

Required environment variables during build:

# Database
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# Authentication
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...

# Site Configuration
NEXT_PUBLIC_SITE_URL=https://judgefinder.io
NODE_ENV=production

Build-time vs Runtime:

  • NEXT_PUBLIC_* variables are embedded at build time
  • Server-side variables are read at runtime
  • Never expose secrets in NEXT_PUBLIC_* variables

Build Output

.next/
├── cache/                    # Build cache (speeds up rebuilds)
├── server/                   # Server-side bundles
│   ├── app/                  # App router pages
│   ├── pages/                # API routes
│   └── chunks/               # Shared chunks
├── static/                   # Static assets
│   ├── css/                  # Compiled CSS
│   ├── chunks/               # JavaScript chunks
│   └── media/                # Optimized images
└── build-manifest.json       # Build metadata

Environment Setup

Development Environment

# Copy example environment file
cp .env.example .env.local

# Install dependencies
npm install

# Validate environment
npm run validate:env

# Start development server
npm run dev

Required Variables:

# Database (Supabase)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key
SUPABASE_JWT_SECRET=your-jwt-secret

# Authentication (Clerk)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Payment (Stripe - Test Mode)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_test_...

# Site Configuration
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NODE_ENV=development

Staging Environment

Staging mimics production but uses test credentials:

# Database - Separate staging instance
NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=staging-service-key

# Authentication - Clerk staging instance
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Payment - Stripe test mode
STRIPE_SECRET_KEY=sk_test_...

# Site Configuration
NEXT_PUBLIC_SITE_URL=https://staging.judgefinder.io
NODE_ENV=production

Production Environment

Production uses live credentials:

# Database - Production Supabase
NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=prod-service-key

# Authentication - Clerk production
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...

# Payment - Stripe live mode
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_live_...

# Site Configuration
NEXT_PUBLIC_SITE_URL=https://judgefinder.io
NODE_ENV=production

# Additional Production Variables
ENCRYPTION_KEY=your-32-byte-encryption-key
CRON_SECRET=your-cron-secret-for-scheduled-jobs
SENTRY_DSN=https://...@sentry.io/...

Environment Variable Security

Never commit:

  • .env.local
  • .env.production
  • Any file with real credentials

Safe to commit:

  • .env.example (template with placeholder values)
  • ENV_VARS_REFERENCE.md (documentation)

Rotation Schedule:

  • API keys: Every 90 days
  • Database passwords: Every 180 days
  • Encryption keys: Annually or after security incident
  • Webhook secrets: After any suspected compromise

Netlify Deployment

Initial Setup

  1. Connect Repository:

    • Go to Netlify Dashboard
    • Click "Add new site" > "Import an existing project"
    • Connect to GitHub repository
    • Select JudgeFinderPlatform repository
  2. Build Settings:

    Base directory: (leave empty)
    Build command: npm run build
    Publish directory: .next
    
  3. Environment Variables:

    • Go to Site settings > Environment variables
    • Add all production variables from .env.example
    • Ensure NODE_ENV=production
  4. Deploy:

    • Click "Deploy site"
    • First deploy takes 3-5 minutes
    • Subsequent deploys take 1-2 minutes

Build Settings (netlify.toml)

The project includes a comprehensive netlify.toml:

[build]
  command = "npm run build"
  # publish directory managed by @netlify/plugin-nextjs

[build.environment]
  NODE_VERSION = "20"
  NODE_OPTIONS = "--max-old-space-size=4096"
  NEXT_TELEMETRY_DISABLED = "1"

# Context-specific builds
[context.production]
  command = "npm run build"

[context.deploy-preview]
  command = "npm run build"

[context.branch-deploy]
  command = "npm run build"

Deploy Previews

Netlify automatically creates deploy previews for pull requests:

  1. PR Created: Netlify builds a preview
  2. Preview URL: Unique URL like deploy-preview-123--judgefinder.netlify.app
  3. Testing: Test changes in isolation before merge
  4. Auto-update: Previews rebuild on every push to PR branch

Using Deploy Previews:

# Push feature branch
git checkout -b feature/my-feature
git push origin feature/my-feature

# Create PR on GitHub
# Netlify comment appears with preview link

# Test preview
# Click "Visit preview" button in PR

Custom Domain Setup

  1. Add Domain:

    • Go to Site settings > Domain management
    • Click "Add custom domain"
    • Enter judgefinder.io
  2. Configure DNS:

    Type: CNAME
    Name: www
    Value: judgefinder.netlify.app
    
    Type: A
    Name: @
    Value: 75.2.60.5
    
  3. Enable HTTPS:

    • Netlify automatically provisions SSL certificate
    • Force HTTPS in Site settings
  4. Verify:

    curl -I https://judgefinder.io
    # Should return 200 OK

Environment Configuration

Production:

# Set via Netlify UI
# Site settings > Environment variables > Add variable

ENCRYPTION_KEY=<32-byte-key>
STRIPE_SECRET_KEY=sk_live_...
SUPABASE_SERVICE_ROLE_KEY=<service-key>

Deploy Context Variables:

# Different values per context
# Production: sk_live_...
# Deploy previews: sk_test_...

Serverless Functions

API routes are deployed as Netlify Functions:

[functions]
  node_bundler = "esbuild"
  external_node_modules = ["@supabase/supabase-js", "sharp"]

[functions.timeout]
  default = 10  # 10 seconds
  api = 26      # 26 seconds for API routes

Long-running Functions:

# 5-minute timeout for sync operations
[functions."api/sync/courts/route"]
  node_bundler = "esbuild"
  [[functions."api/sync/courts/route".timeout]]
    maxDuration = 300

Scheduled Functions (Cron Jobs)

# Daily sync at 2 AM UTC
[functions."api/cron/daily-sync/route"]
  schedule = "0 2 * * *"
  node_bundler = "esbuild"

# Weekly sync Sunday 3 AM UTC
[functions."api/cron/weekly-sync/route"]
  schedule = "0 3 * * 0"
  node_bundler = "esbuild"

Securing Cron Jobs:

// app/api/cron/daily-sync/route.ts
export async function POST(request: Request) {
  // Verify cron secret
  const authHeader = request.headers.get('Authorization')
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response('Unauthorized', { status: 401 })
  }

  // Execute sync
  await performDailySync()

  return new Response('OK', { status: 200 })
}

CI/CD Pipeline

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy to Netlify

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Type check
        run: npm run type-check

      - name: Run tests
        run: npm run test:ci
        env:
          DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}

      - name: Build
        run: npm run build
        env:
          NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
          NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Netlify
        uses: netlify/actions/cli@master
        with:
          args: deploy --prod
        env:
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

Automated Testing

# Run in CI/CD
npm run test:ci

# This runs:
# - npm run lint           # Code linting
# - npm run type-check     # TypeScript validation
# - npm run test:coverage  # Tests with coverage

Deploy on Merge

# Automatic deployment flow:
1. Create feature branch
2. Push commits
3. Open pull request
4. CI runs tests
5. Deploy preview created
6. Review and approve PR
7. Merge to main
8. Production deployment triggered
9. Site live in 1-2 minutes

Rollback Procedures

Netlify Rollback (instant):

# Via Netlify UI:
1. Go to Deploys
2. Find previous successful deploy
3. Click "..." menu
4. Select "Publish deploy"
5. Previous version live immediately

# Via CLI:
netlify deploy --prod --alias=rollback-v1.2.3

Git Rollback:

# Create rollback PR
git checkout main
git revert <bad-commit-sha>
git push origin main

# Or hard reset (use with caution)
git reset --hard <good-commit-sha>
git push --force origin main

Database Rollback:

# If migration caused issue
npm run migration:rollback

# Restore from backup
npm run migration:restore <backup-id>

Database Migrations

Migration Files

Migrations are stored in supabase/migrations/:

supabase/migrations/
├── 20231201_create_judges_table.sql
├── 20231202_add_analytics_views.sql
├── 20231203_create_rls_policies.sql
└── 20231204_add_indexes.sql

Running Migrations

Development:

# Apply all pending migrations
supabase db push

# Create new migration
supabase migration new add_feature_name

# Reset database (destructive!)
supabase db reset

Production:

# Via Supabase Dashboard:
1. Go to Database > Migrations
2. Review pending migrations
3. Click "Apply migrations"
4. Confirm changes

# Via CLI (automated):
npm run migration:apply

Migration Best Practices

-- ✅ Good - Reversible migration
BEGIN;

-- Add new column with default
ALTER TABLE judges ADD COLUMN email TEXT;

-- Create index
CREATE INDEX idx_judges_email ON judges(email);

-- Commit transaction
COMMIT;

-- ❌ Bad - Irreversible data loss
DROP TABLE judges;  -- Never drop production tables!

Safety Checklist:

  • Test migration on staging first
  • Backup database before applying
  • Write rollback script
  • Verify no data loss
  • Check application still works
  • Monitor error logs for 1 hour

Rollback Strategy

-- Forward migration (20231204_add_email.sql)
ALTER TABLE judges ADD COLUMN email TEXT;
CREATE INDEX idx_judges_email ON judges(email);

-- Rollback migration (20231204_add_email_rollback.sql)
DROP INDEX IF EXISTS idx_judges_email;
ALTER TABLE judges DROP COLUMN IF EXISTS email;
# Apply rollback
npm run migration:rollback 20231204_add_email

Zero-Downtime Migrations

For large tables, use multi-step migrations:

-- Step 1: Add column with default (non-blocking)
ALTER TABLE judges ADD COLUMN status TEXT DEFAULT 'active';

-- Step 2: Backfill data (background job)
-- Run: UPDATE judges SET status = 'active' WHERE status IS NULL;

-- Step 3: Add NOT NULL constraint (after backfill complete)
ALTER TABLE judges ALTER COLUMN status SET NOT NULL;

Monitoring

Error Tracking (Sentry)

Setup:

# Install Sentry
npm install @sentry/nextjs

# Initialize
npx @sentry/wizard@latest -i nextjs

Configuration:

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.1,

  beforeSend(event) {
    // Filter out development errors
    if (process.env.NODE_ENV === 'development') {
      return null
    }
    return event
  },
})

Error Reporting:

// Automatic error capture
try {
  await riskyOperation()
} catch (error) {
  Sentry.captureException(error, {
    tags: { component: 'JudgeSearch' },
    extra: { query, userId },
  })
  throw error
}

Performance Monitoring

Core Web Vitals:

  • LCP (Largest Contentful Paint): <2.5s
  • FID (First Input Delay): <100ms
  • CLS (Cumulative Layout Shift): <0.1

Monitor via:

  • Netlify Analytics
  • Sentry Performance
  • Google Search Console

API Latency:

// app/api/judges/search/route.ts
const startTime = Date.now()

const results = await searchJudges(query)

const latency = Date.now() - startTime

// Log to monitoring service
logger.info('search_latency', { latency, query, resultCount: results.length })

Database Performance

Query Monitoring:

-- View slow queries (Supabase Dashboard)
SELECT
  query,
  mean_exec_time,
  calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

Index Usage:

-- Check index efficiency
SELECT
  schemaname,
  tablename,
  indexname,
  idx_scan,
  idx_tup_read
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC;

Cache Hit Rate

Redis Analytics:

// Monitor cache performance
const stats = await redis.info('stats')

const hitRate = stats.keyspace_hits / (stats.keyspace_hits + stats.keyspace_misses)

console.log(`Cache hit rate: ${(hitRate * 100).toFixed(2)}%`)

Target: >90% cache hit rate for frequently accessed data

Log Aggregation

Netlify Functions Logs:

# View live logs
netlify dev --live

# View production logs
netlify functions:logs

Structured Logging:

// lib/logger.ts
export function logSearch(query: string, resultCount: number, userId?: string) {
  console.log(
    JSON.stringify({
      event: 'search',
      query,
      resultCount,
      userId,
      timestamp: new Date().toISOString(),
    })
  )
}

Production Checklist

Pre-Deployment

  • All tests passing (npm run test:ci)
  • TypeScript compiles (npm run type-check)
  • Linter passes (npm run lint)
  • Build succeeds locally (npm run build)
  • Environment variables verified (npm run validate:env)
  • Database migrations tested on staging
  • Secrets rotated if needed
  • Changelog updated
  • Documentation updated

Deployment

  • Create release branch: release/v1.2.3
  • Tag release: git tag v1.2.3
  • Push to main: git push origin main --tags
  • Monitor deployment progress on Netlify
  • Verify build completes successfully
  • Check deploy logs for warnings

Post-Deployment Verification

Automated Checks:

# Run smoke tests
npm run test:e2e -- --grep "critical"

# Check health endpoint
curl https://judgefinder.io/api/health

Manual Verification:

  • Homepage loads correctly
  • Search functionality works
  • Judge profiles display
  • Authentication works (sign in/out)
  • Subscription flow works
  • API endpoints respond
  • No console errors in browser
  • Mobile responsive
  • Dark mode works

Performance Checks:

  • Lighthouse score >90
  • LCP <2.5s
  • Page load time <3s
  • API latency <500ms

Monitoring:

  • No error spike in Sentry
  • Cache hit rate normal
  • Database CPU <50%
  • Memory usage stable

Rollback Plan

If issues detected:

  1. Immediate: Rollback via Netlify UI (30 seconds)
  2. If database changed: Restore backup (5 minutes)
  3. Verify rollback: Run smoke tests
  4. Communicate: Post status update
  5. Debug: Fix issue in development
  6. Redeploy: When fix verified

Troubleshooting

Build Failures

Error: "Module not found"

# Clear cache and reinstall
rm -rf node_modules .next
npm install
npm run build

Error: "Out of memory"

# Increase Node.js memory
NODE_OPTIONS=--max-old-space-size=4096 npm run build

Error: "Environment variable undefined"

# Verify all required variables set
npm run validate:env

# Check Netlify environment variables
netlify env:list

Deployment Issues

Deploy preview not working:

  1. Check GitHub integration connected
  2. Verify Netlify app permissions
  3. Ensure base directory is root
  4. Check build command matches netlify.toml

Functions timing out:

# Increase timeout in netlify.toml
[functions."api/slow-endpoint/route"]
  [[functions."api/slow-endpoint/route".timeout]]
    maxDuration = 60  # Increase to 60 seconds

Domain not resolving:

# Check DNS propagation
dig judgefinder.io

# Verify CNAME record
nslookup www.judgefinder.io

# Check SSL certificate
curl -vI https://judgefinder.io

Database Issues

Connection pool exhausted:

// Increase pool size in Supabase
// Database settings > Connection pooling
// Set max connections: 15 → 25

Slow queries:

-- Add missing indexes
CREATE INDEX idx_judges_name ON judges(name);
CREATE INDEX idx_cases_judge_id ON cases(judge_id);

-- Analyze query plan
EXPLAIN ANALYZE SELECT * FROM judges WHERE name ILIKE '%smith%';

Migration failed:

# Rollback migration
npm run migration:rollback

# Fix migration file
# Reapply migration
npm run migration:apply

Cache Issues

Stale data served:

// Invalidate cache
await redis.del('search:*')

// Or set shorter TTL
withRedisCache(key, 60, async () => { ... })  // 60 seconds

Cache miss rate high:

// Check Redis connection
redis.ping()

// Verify cache keys are consistent
buildCacheKey('search', { query: 'smith' })

Support Escalation

Level 1 (Development Team):

  • Application errors
  • Feature bugs
  • Code issues

Level 2 (Infrastructure Team):

  • Netlify deployment issues
  • DNS problems
  • SSL certificate issues

Level 3 (External Services):


Emergency Contacts

On-Call Engineer: Available 24/7 for critical issues DevOps Lead: For infrastructure issues CTO: For major incidents requiring executive decision

Critical Incident Protocol:

  1. Assess severity (P0/P1/P2/P3)
  2. Page on-call engineer
  3. Create incident channel
  4. Execute rollback if needed
  5. Post status updates
  6. Root cause analysis within 24h

Related Documentation


Last Updated: 2025-10-26 Maintained By: JudgeFinder Development Team