• ShipSaaSDocumentation
  • Introduction
  • Getting Started
    • Installation
    • Environment Setup
    • Database Setup
    • Development Guide
  • Database
    • Repository Pattern
    • Drizzle Queries
    • Migration Workflow
    • Drizzle Studio
    • Best Practices
  • File Management
    • File Upload Component
    • Server Actions
    • Storage Configuration
  • Authentication Methods
    • Credentials Authentication
    • Magic Link Authentication
    • Two-Factor Authentication (2FA)
    • Social Authentication
    • API Keys & Bearer Tokens
    • Organizations (Multi-tenant)
  • Authorization & Permissions
    • Roles & Hierarchy
    • Route Protection
    • API Protection
    • CASL Permissions
  • Email & Notifications
    • Resend Setup
    • React Email Templates
    • Notification System
    • Email Workflows
    • Customization & Extension
  • Stripe & Payments
    • Stripe Setup
    • Better Auth Integration
    • Checkout Methods
    • Subscription Management
    • Admin Dashboard
    • Webhook Events
  • Architecture & Development
    • Presentation Layer
    • Service Layer
    • Persistence Layer
    • Helpers Architecture
    • Project Structure
    • Development Workflow
  • Testing
    • General Testing
    • Unit Testing
    • UI Component Testing
    • Repository Testing
    • Service Testing
    • End-to-End Testing
  • Deployment
    • Vercel Deployment
    • GitHub Actions CI/CD
    • Database Backup & Restore
    • Database Migrations
    • Troubleshooting Deployment
  • AI Agents Integration
  • Blog Management
    • Blog Setup
    • MDX Blog
    • Database Blog
  • Async Tasks
    • Setup
    • Email Workflows
    • Payment Processing
    • Maintenance Jobs
  1. Documentation
  2. Deployment
  3. Troubleshooting Deployment
Buydocschangelogsdemocommunity

Troubleshooting Deployment

PreviousDatabase MigrationsNextAI Agents Integration
On this page
Common Deployment IssuesGitHub Actions FailuresTesting FailuresDatabase Migration FailuresVercel Deployment IssuesBuild and Function IssuesEnvironment Variable IssuesDatabase Connection IssuesConnection ProblemsSchema IssuesAuthentication & Better Auth IssuesAuthentication FailuresSession IssuesStripe Payment IssuesWebhook ProblemsPayment Flow IssuesFile Upload & Supabase IssuesStorage ProblemsPerformance & Optimization IssuesSlow PerformanceDatabase PerformanceDebugging TechniquesLogging and MonitoringHealth Check EndpointsEmergency Procedures

Comprehensive troubleshooting guide for common deployment issues and their solutions.

Common Deployment Issues

Quick reference for the most frequent deployment problems:

GitHub Actions Failures

Build Failures

Issue: Build fails with missing environment variables

bash
# Error in GitHub Actions logs:
Error: Environment variable DATABASE_URL is not defined

Solution:

  1. Check GitHub repository secrets
  2. Verify secret names match workflow exactly
  3. Add missing secrets in Repository Settings → Secrets and variables → Actions
bash
# Required secrets in GitHub:
DATABASE_URL                    # ✅ Must exist
BETTER_AUTH_SECRET             # ✅ Must exist
RESEND_API_KEY                 # ✅ Must exist
SUPABASE_ANON_KEY             # ✅ Must exist
STRIPE_SECRET_KEY             # ✅ Must exist
STRIPE_WEBHOOK_SECRET         # ✅ Must exist
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY # ✅ Must exist

Issue: Build succeeds locally but fails in CI

bash
# Error:
Type 'string | undefined' is not assignable to type 'string'

Solution: Add environment variable validation

typescript
// lib/env.ts
export const requiredEnvVars = {
  DATABASE_URL: process.env.DATABASE_URL!,
  BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET!,
  // ... other required vars
}

// Check at app startup
Object.entries(requiredEnvVars).forEach(([key, value]) => {
  if (!value) {
    throw new Error(`Missing required environment variable: ${key}`)
  }
})

Linting Failures

Issue: ESLint errors block deployment

bash
# Error:
✖ 12 problems (8 errors, 4 warnings)

Solution:

bash
# Fix linting issues locally
pnpm lint:fix

# Or disable specific rules if needed
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */

# Commit fixes
git add .
git commit -m "Fix linting issues"
git push

Testing Failures

Issue: Tests pass locally but fail in CI

bash
# Error:
FAIL src/services/__tests__/user-service.test.ts
Database connection failed

Solution: Check test database configuration

typescript
// Ensure test environment uses correct DATABASE_URL
// In GitHub Actions workflow, tests use same DB as build
# Check that test database is accessible from GitHub Actions

Issue: Test timeout in CI environment

bash
# Error:
Test timeout after 5000ms

Solution: Increase timeout and optimize tests

typescript
// In test files
test('should complete operation', async () => {
  // ... test code
}, 10000) // Increase timeout to 10s

// Or configure globally in vitest.config.ts
export default defineConfig({
  test: {
    testTimeout: 10000
  }
})

Database Migration Failures

Issue: Migration fails in production

bash
# Error in GitHub Actions:
❌ Migration failed
relation "uuid-ossp" does not exist

Solution: Ensure UUID extension creation

typescript
// In migration script, ensure UUID extension is created first
await db.execute(sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`)

Issue: Migration timeout

bash
# Error:
Migration timeout after 30 seconds

Solution: Break large migrations into smaller chunks

sql
-- Instead of one large migration:
-- ALTER TABLE users ADD COLUMN a text, ADD COLUMN b text, ...;

-- Break into multiple migrations:
-- Migration 1:
ALTER TABLE users ADD COLUMN a text;

-- Migration 2:
ALTER TABLE users ADD COLUMN b text;

Vercel Deployment Issues

Build and Function Issues

Issue: Vercel build timeout

bash
# Error in Vercel logs:
Build timeout after 15 minutes

Solution: Optimize build process

typescript
// next.config.ts - optimize build
const nextConfig = {
  // Reduce build time
  typescript: {
    ignoreBuildErrors: false, // Keep false for safety
  },
  eslint: {
    ignoreDuringBuilds: false, // Keep false for safety
  },

  // Optimize output
  output: 'standalone', // For Docker deployment if needed

  // Reduce bundle size
  experimental: {
    optimizePackageImports: ['lodash', 'date-fns'],
  },
}

Issue: Serverless function timeout

bash
# Error:
Function Timeout after 30 seconds

Solution: Optimize function performance and increase timeout

json
// vercel.json
{
  "functions": {
    "app/api/**/*.ts": {
      "maxDuration": 60  // Increase to 60 seconds
    }
  }
}
typescript
// Optimize database queries
// Use connection pooling
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20, // Maximum connections
  idleTimeoutMillis: 30000,
})

Environment Variable Issues

Issue: Environment variables not available in Vercel

bash
# Error:
process.env.STRIPE_SECRET_KEY is undefined

Solution:

  1. Check Vercel Project Settings → Environment Variables
  2. Ensure variables are set for correct environment (Production/Preview/Development)
  3. Verify NEXT_PUBLIC_ prefix for client-side variables

Issue: Different behavior between Vercel environments

bash
# Works in development, fails in production

Solution: Check environment-specific configuration

typescript
// Debug environment variables
console.log('Environment:', process.env.NODE_ENV)
console.log('Vercel Environment:', process.env.VERCEL_ENV)
console.log('Database URL exists:', !!process.env.DATABASE_URL)

// Environment-specific logic
if (process.env.VERCEL_ENV === 'production') {
  // Production-specific configuration
} else if (process.env.VERCEL_ENV === 'preview') {
  // Preview-specific configuration
}

Database Connection Issues

Connection Problems

Issue: Database connection refused

bash
# Error:
connect ECONNREFUSED

Solution: Check database URL and network access

bash
# Verify DATABASE_URL format
postgres://user:password@host:port/database?sslmode=require

# Test connection manually
psql $DATABASE_URL -c "SELECT 1"

# Common fixes:
# 1. Add ?sslmode=require for managed databases
# 2. Check firewall/security group settings
# 3. Verify credentials are correct

Issue: Too many database connections

bash
# Error:
remaining connection slots are reserved for non-replication superuser connections

Solution: Implement connection pooling

typescript
// lib/db.ts
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'

// Use connection pooling
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,           // Maximum number of connections
  min: 5,            // Minimum number of connections
  idle: 10000,       // Close connections after 10 seconds of inactivity
  connectionTimeoutMillis: 5000,
})

export const db = drizzle(pool)

Schema Issues

Issue: Schema out of sync

bash
# Error:
column "new_field" of relation "users" does not exist

Solution: Ensure migrations are applied

bash
# Check migration status
pnpm db:migrate

# If still failing, check __drizzle_migrations table
psql $DATABASE_URL -c "SELECT * FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 5"

# Re-run migrations if needed
pnpm db:push --force  # Only in development!

Authentication & Better Auth Issues

Authentication Failures

Issue: Better Auth throws session errors

bash
# Error:
Invalid session token

Solution: Check Better Auth configuration

typescript
// Check BETTER_AUTH_SECRET is set consistently
// Verify BETTER_AUTH_URL matches deployment URL

// In production:
BETTER_AUTH_URL=https://yourdomain.com

// In preview:
BETTER_AUTH_URL=https://yourapp-git-dev-team.vercel.app

Issue: OAuth providers not working

bash
# Error:
OAuth provider error: invalid_client

Solution: Update OAuth provider settings

bash
# Google OAuth Console:
# Add authorized redirect URIs:
# https://yourdomain.com/api/auth/callback/google
# https://yourapp-git-dev-team.vercel.app/api/auth/callback/google

# GitHub OAuth App:
# Update Authorization callback URL:
# https://yourdomain.com/api/auth/callback/github

Session Issues

Issue: Users logged out randomly

bash
# Error:
Session expired unexpectedly

Solution: Check session configuration

typescript
// Better Auth session config
export const auth = betterAuth({
  session: {
    expiresIn: 60 * 60 * 24 * 30, // 30 days
    updateAge: 60 * 60 * 24,       // Update every 24 hours
    cookieCache: {
      enabled: true,
      maxAge: 60 * 5,              // 5 minutes
    },
  },
  // ...
})

Stripe Payment Issues

Webhook Problems

Issue: Stripe webhooks failing

bash
# Error in Stripe Dashboard:
Webhook endpoint returned 500

Solution: Debug webhook endpoint

typescript
// app/api/auth/stripe/webhook/route.ts
export async function POST(request: Request) {
  try {
    const body = await request.text()
    const signature = request.headers.get('stripe-signature')

    console.log('Webhook received:', {
      hasBody: !!body,
      hasSignature: !!signature,
      webhookSecret: !!process.env.STRIPE_WEBHOOK_SECRET
    })

    // Verify webhook signature
    const event = stripe.webhooks.constructEvent(
      body,
      signature!,
      process.env.STRIPE_WEBHOOK_SECRET!
    )

    console.log('Webhook event:', event.type)

    // Handle event...

  } catch (error) {
    console.error('Webhook error:', error)
    return new Response('Webhook Error', { status: 400 })
  }

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

Issue: Webhook signature verification fails

bash
# Error:
No signatures found matching the expected signature for payload

Solution: Check webhook secret and payload handling

typescript
// Ensure raw body is used for signature verification
// Don't parse JSON before signature verification

// Correct approach:
const body = await request.text()  // Raw text body
const event = stripe.webhooks.constructEvent(body, signature, secret)

// Incorrect approach:
const body = await request.json()  // Parsed JSON breaks signature

Payment Flow Issues

Issue: Payments not processing

bash
# Error:
Payment failed with status: requires_action

Solution: Handle 3D Secure authentication

typescript
// Handle different payment statuses
switch (paymentIntent.status) {
  case 'requires_payment_method':
    // Payment method required
    break
  case 'requires_action':
    // 3D Secure authentication required
    // Redirect user to authentication
    break
  case 'succeeded':
    // Payment successful
    break
  case 'requires_confirmation':
    // Confirm payment intent
    break
}

File Upload & Supabase Issues

Storage Problems

Issue: File uploads failing

bash
# Error:
Supabase storage: Unauthorized

Solution: Check Supabase bucket policies

sql
-- In Supabase SQL Editor:
-- Allow authenticated users to upload files
INSERT INTO storage.buckets (id, name, public)
VALUES ('your-bucket-name', 'Your Bucket', true);

-- Set bucket policy
CREATE POLICY "Allow authenticated uploads"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'your-bucket-name');

-- Allow public read access
CREATE POLICY "Allow public reads"
ON storage.objects FOR SELECT
TO public
USING (bucket_id = 'your-bucket-name');

Issue: File size limit exceeded

bash
# Error:
File size exceeds maximum allowed size

Solution: Configure file size limits

typescript
// Environment variable
MAX_FILE_SIZE=5242880  // 5MB in bytes

// In upload handler
const maxSize = parseInt(process.env.MAX_FILE_SIZE || '5242880')
if (file.size > maxSize) {
  throw new Error(`File size exceeds ${maxSize} bytes`)
}

Performance & Optimization Issues

Slow Performance

Issue: Application loading slowly

bash
# Symptoms:
# - Long Time to First Byte (TTFB)
# - Slow page loads
# - High bounce rate

Solution: Implement performance optimizations

typescript
// next.config.ts optimizations
const nextConfig = {
  // Image optimization
  images: {
    formats: ['image/webp', 'image/avif'],
    minimumCacheTTL: 60,
  },

  // Compression
  compress: true,

  // Caching headers
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 's-maxage=60, stale-while-revalidate=300'
          }
        ]
      }
    ]
  },

  // Static optimization
  experimental: {
    optimizePackageImports: ['lodash', '@radix-ui/react-icons'],
  },
}

Database Performance

Issue: Slow database queries

bash
# Symptoms:
# - API endpoints timing out
# - Long response times
# - High database CPU

Solution: Optimize database queries

typescript
// Add database indexes
await db.execute(sql`
  CREATE INDEX IF NOT EXISTS idx_user_email ON users(email);
  CREATE INDEX IF NOT EXISTS idx_subscription_user_id ON subscription(reference_id);
  CREATE INDEX IF NOT EXISTS idx_organization_slug ON organization(slug);
`)

// Use pagination for large datasets
const users = await db.select()
  .from(usersTable)
  .limit(20)
  .offset(page * 20)

// Use select only needed columns
const users = await db.select({
  id: usersTable.id,
  name: usersTable.name,
  email: usersTable.email,
})
.from(usersTable)

Debugging Techniques

Logging and Monitoring

Application Logging:

typescript
// lib/logger.ts
import winston from 'winston'

export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    // Add file transport for production
  ],
})

// Usage in API routes
import { logger } from '@/lib/logger'

export async function POST(request: Request) {
  logger.info('API endpoint called', {
    url: request.url,
    method: request.method,
    headers: Object.fromEntries(request.headers.entries())
  })

  try {
    // ... API logic
    logger.info('API endpoint completed successfully')
  } catch (error) {
    logger.error('API endpoint failed', {
      error: error instanceof Error ? error.message : 'Unknown error',
      stack: error instanceof Error ? error.stack : undefined
    })
    throw error
  }
}

Database Query Debugging:

typescript
// Enable query logging in development
const db = drizzle(client, {
  logger: process.env.NODE_ENV === 'development' ? {
    logQuery: (query, params) => {
      console.log('Query:', query)
      console.log('Params:', params)
    }
  } : undefined
})

Health Check Endpoints

Create comprehensive health checks:

typescript
// app/api/health/route.ts
export async function GET() {
  const checks = {
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV,
    version: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 8) || 'unknown',
    status: 'healthy',
    checks: {} as Record<string, any>,
  }

  try {
    // Database check
    const dbStart = Date.now()
    await db.select().from(users).limit(1)
    checks.checks.database = {
      status: 'healthy',
      responseTime: Date.now() - dbStart
    }

    // Email service check
    if (process.env.RESEND_API_KEY) {
      checks.checks.email = { status: 'configured' }
    }

    // Stripe check
    if (process.env.STRIPE_SECRET_KEY) {
      checks.checks.stripe = { status: 'configured' }
    }

    // File storage check
    if (process.env.SUPABASE_URL) {
      checks.checks.storage = { status: 'configured' }
    }

    return Response.json(checks)

  } catch (error) {
    checks.status = 'unhealthy'
    checks.checks.database = {
      status: 'unhealthy',
      error: error instanceof Error ? error.message : 'Unknown error'
    }

    return Response.json(checks, { status: 503 })
  }
}

Emergency Procedures

When production is completely broken:

  1. Immediate Rollback:

    bash
    # Via Vercel Dashboard:
    # 1. Go to Project → Deployments
    # 2. Find last working deployment
    # 3. Click "Promote to Production"
    
    # Via CLI:
    vercel ls
    vercel promote [previous-deployment-url]
  2. Check System Status:

    bash
    # Health check
    curl https://yourdomain.com/api/health
    
    # Database connectivity
    psql $DATABASE_URL -c "SELECT 1"
    
    # External services
    curl -H "Authorization: Bearer $RESEND_API_KEY" https://api.resend.com/domains
  3. Monitor and Fix:

    bash
    # Check GitHub Actions logs
    # Review Vercel function logs
    # Monitor database performance
    # Check external service status pages

Your deployment troubleshooting toolkit is now complete. These solutions cover 95% of common deployment issues you'll encounter. Remember to always test fixes in preview environments before applying to production.