Complete deployment documentation for JudgeFinder Platform
Version: 1.0.0 Last Updated: 2025-10-26 Status: Production
- Build Process
- Environment Setup
- Netlify Deployment
- CI/CD Pipeline
- Database Migrations
- Monitoring
- Production Checklist
- Troubleshooting
# Start development server
npm run dev
# Development server with Turbo (faster)
npm run dev:turboDevelopment Features:
- Hot module replacement (HMR)
- Source maps enabled
- Error overlay in browser
- No minification
- Fast refresh for React components
# Validate environment variables first
npm run validate:env
# Build for production
npm run build
# Test production build locally
npm run startNext.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 buildThis opens a visual bundle analyzer showing:
- Size of each module
- Which libraries are included
- Opportunities for optimization
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=productionBuild-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
.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
# Copy example environment file
cp .env.example .env.local
# Install dependencies
npm install
# Validate environment
npm run validate:env
# Start development server
npm run devRequired 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=developmentStaging 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=productionProduction 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/...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
-
Connect Repository:
- Go to Netlify Dashboard
- Click "Add new site" > "Import an existing project"
- Connect to GitHub repository
- Select
JudgeFinderPlatformrepository
-
Build Settings:
Base directory: (leave empty) Build command: npm run build Publish directory: .next -
Environment Variables:
- Go to Site settings > Environment variables
- Add all production variables from
.env.example - Ensure
NODE_ENV=production
-
Deploy:
- Click "Deploy site"
- First deploy takes 3-5 minutes
- Subsequent deploys take 1-2 minutes
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"Netlify automatically creates deploy previews for pull requests:
- PR Created: Netlify builds a preview
- Preview URL: Unique URL like
deploy-preview-123--judgefinder.netlify.app - Testing: Test changes in isolation before merge
- 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-
Add Domain:
- Go to Site settings > Domain management
- Click "Add custom domain"
- Enter
judgefinder.io
-
Configure DNS:
Type: CNAME Name: www Value: judgefinder.netlify.app Type: A Name: @ Value: 75.2.60.5 -
Enable HTTPS:
- Netlify automatically provisions SSL certificate
- Force HTTPS in Site settings
-
Verify:
curl -I https://judgefinder.io # Should return 200 OK
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_...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 routesLong-running Functions:
# 5-minute timeout for sync operations
[functions."api/sync/courts/route"]
node_bundler = "esbuild"
[[functions."api/sync/courts/route".timeout]]
maxDuration = 300# 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 })
}# .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 }}# 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# 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 minutesNetlify 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.3Git 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 mainDatabase Rollback:
# If migration caused issue
npm run migration:rollback
# Restore from backup
npm run migration:restore <backup-id>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
Development:
# Apply all pending migrations
supabase db push
# Create new migration
supabase migration new add_feature_name
# Reset database (destructive!)
supabase db resetProduction:
# 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-- ✅ 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
-- 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_emailFor 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;Setup:
# Install Sentry
npm install @sentry/nextjs
# Initialize
npx @sentry/wizard@latest -i nextjsConfiguration:
// 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
}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 })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;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
Netlify Functions Logs:
# View live logs
netlify dev --live
# View production logs
netlify functions:logsStructured 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(),
})
)
}- 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
- 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
Automated Checks:
# Run smoke tests
npm run test:e2e -- --grep "critical"
# Check health endpoint
curl https://judgefinder.io/api/healthManual 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
If issues detected:
- Immediate: Rollback via Netlify UI (30 seconds)
- If database changed: Restore backup (5 minutes)
- Verify rollback: Run smoke tests
- Communicate: Post status update
- Debug: Fix issue in development
- Redeploy: When fix verified
Error: "Module not found"
# Clear cache and reinstall
rm -rf node_modules .next
npm install
npm run buildError: "Out of memory"
# Increase Node.js memory
NODE_OPTIONS=--max-old-space-size=4096 npm run buildError: "Environment variable undefined"
# Verify all required variables set
npm run validate:env
# Check Netlify environment variables
netlify env:listDeploy preview not working:
- Check GitHub integration connected
- Verify Netlify app permissions
- Ensure base directory is root
- 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 secondsDomain not resolving:
# Check DNS propagation
dig judgefinder.io
# Verify CNAME record
nslookup www.judgefinder.io
# Check SSL certificate
curl -vI https://judgefinder.ioConnection pool exhausted:
// Increase pool size in Supabase
// Database settings > Connection pooling
// Set max connections: 15 → 25Slow 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:applyStale data served:
// Invalidate cache
await redis.del('search:*')
// Or set shorter TTL
withRedisCache(key, 60, async () => { ... }) // 60 secondsCache miss rate high:
// Check Redis connection
redis.ping()
// Verify cache keys are consistent
buildCacheKey('search', { query: 'smith' })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):
- Supabase: support@supabase.io
- Clerk: support@clerk.dev
- Stripe: support@stripe.com
- Netlify: support@netlify.com
On-Call Engineer: Available 24/7 for critical issues DevOps Lead: For infrastructure issues CTO: For major incidents requiring executive decision
Critical Incident Protocol:
- Assess severity (P0/P1/P2/P3)
- Page on-call engineer
- Create incident channel
- Execute rollback if needed
- Post status updates
- Root cause analysis within 24h
- ARCHITECTURE.md - System architecture
- TESTING_GUIDE.md - Testing documentation
- API_DOCUMENTATION.md - API reference
- CONTRIBUTING.md - Development guidelines
Last Updated: 2025-10-26 Maintained By: JudgeFinder Development Team