Skip to content

deploy-production

Access: /template deploy-production or /t deploy-production

Guides you through deploying your MakerKit application to production, including environment setup, database migrations, and monitoring configuration.

What It Does

The deploy-production command helps you:

  • Configure production environment variables
  • Set up production database
  • Configure CDN and caching
  • Set up monitoring and error tracking
  • Configure custom domains
  • Implement security headers
  • Set up CI/CD pipelines

Usage

bash
/template deploy-production "Description"
# or
/t deploy-production "Description"

When prompted, specify:

  • Deployment platform (Vercel, AWS, etc.)
  • Environment configuration
  • Domain settings
  • Monitoring preferences

Prerequisites

  • A completed MakerKit application
  • Deployment platform account
  • Domain name (optional)
  • Commercial MakerKit license from MakerKit

What Gets Created

1. Environment Configuration

Production environment setup:

bash
# .env.production
# Application
NEXT_PUBLIC_APP_URL=https://your-app.com
NEXT_PUBLIC_APP_NAME="Your App"
NODE_ENV=production

# 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

# Stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Email
RESEND_API_KEY=re_...

# Monitoring
NEXT_PUBLIC_SENTRY_DSN=https://...@sentry.io/...
SENTRY_AUTH_TOKEN=...

# Analytics
NEXT_PUBLIC_GA_ID=G-...
NEXT_PUBLIC_MIXPANEL_TOKEN=...

2. Vercel Deployment

Vercel configuration:

json
// vercel.json
{
  "framework": "nextjs",
  "buildCommand": "pnpm build",
  "devCommand": "pnpm dev",
  "installCommand": "pnpm install",
  "regions": ["iad1"],
  "functions": {
    "app/api/stripe/webhook/route.ts": {
      "maxDuration": 60
    }
  },
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-DNS-Prefetch-Control",
          "value": "on"
        },
        {
          "key": "X-Frame-Options",
          "value": "SAMEORIGIN"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=()"
        }
      ]
    },
    {
      "source": "/_next/static/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ],
  "redirects": [
    {
      "source": "/home",
      "destination": "/dashboard",
      "permanent": true
    }
  ]
}

3. Database Migration Script

Production migration setup:

typescript
// scripts/migrate-production.ts
import { createClient } from '@supabase/supabase-js';
import { config } from 'dotenv';
import { execSync } from 'child_process';

// Load production env
config({ path: '.env.production.local' });

async function migrateProduction() {
  console.log('🚀 Starting production migration...');
  
  // Backup current database
  console.log('📦 Creating database backup...');
  execSync(`pg_dump ${process.env.DATABASE_URL} > backup-${Date.now()}.sql`);
  
  // Run migrations
  console.log('🔄 Running migrations...');
  execSync('pnpm supabase db push --db-url=$DATABASE_URL');
  
  // Verify migrations
  const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!
  );
  
  const { data, error } = await supabase
    .from('schema_migrations')
    .select('version')
    .order('version', { ascending: false })
    .limit(1)
    .single();
  
  if (error) {
    console.error('❌ Migration verification failed:', error);
    process.exit(1);
  }
  
  console.log('✅ Migration completed. Latest version:', data.version);
  
  // Run post-migration tasks
  await runPostMigrationTasks();
}

async function runPostMigrationTasks() {
  // Update search indexes
  console.log('🔍 Updating search indexes...');
  await supabase.rpc('reindex_search_tables');
  
  // Refresh materialized views
  console.log('🔄 Refreshing materialized views...');
  await supabase.rpc('refresh_all_materialized_views');
  
  console.log('✅ Post-migration tasks completed');
}

4. GitHub Actions CI/CD

Automated deployment pipeline:

yaml
# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]
  workflow_dispatch:

env:
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'pnpm'
          
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm typecheck
      - run: pnpm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: pnpm/action-setup@v2
        with:
          version: 8
          
      - run: pnpm install --frozen-lockfile
      
      - name: Pull Vercel Environment
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Build Project
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
        
      - name: Deploy to Vercel
        id: deploy
        run: |
          DEPLOYMENT_URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})
          echo "deployment-url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
          
      - name: Run E2E Tests
        run: |
          DEPLOYMENT_URL=${{ steps.deploy.outputs.deployment-url }} pnpm test:e2e:production
          
      - name: Notify Deployment
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Production deployment completed",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "✅ Production deployment successful\n${{ steps.deploy.outputs.deployment-url }}"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

5. Security Headers

Production security configuration:

typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Security headers
  response.headers.set(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' data:",
      "connect-src 'self' https://api.stripe.com https://*.supabase.co wss://*.supabase.co",
      "frame-src 'self' https://checkout.stripe.com",
      "object-src 'none'",
      "base-uri 'self'",
      "form-action 'self'",
      "frame-ancestors 'none'",
    ].join('; ')
  );
  
  response.headers.set(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains'
  );
  
  return response;
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

6. Production Monitoring

Set up monitoring dashboards:

typescript
// lib/monitoring/production.ts
import * as Sentry from '@sentry/nextjs';
import { CLS, FID, LCP, TTFB, getFCP } from 'web-vitals';

// Initialize monitoring
export function initProductionMonitoring() {
  // Sentry
  Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: 'production',
    integrations: [
      new Sentry.BrowserTracing(),
      new Sentry.Replay({
        maskAllText: false,
        blockAllMedia: false,
      }),
    ],
    tracesSampleRate: 0.1,
    replaysSessionSampleRate: 0.1,
    replaysOnErrorSampleRate: 1.0,
  });
  
  // Web Vitals
  const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
  
  function sendToAnalytics(metric: any) {
    const body = JSON.stringify({
      dsn: process.env.NEXT_PUBLIC_VERCEL_ANALYTICS_ID,
      id: metric.id,
      page: window.location.pathname,
      href: window.location.href,
      event_name: metric.name,
      value: metric.value.toString(),
      speed: navigator.connection?.effectiveType || '',
    });
    
    if (navigator.sendBeacon) {
      navigator.sendBeacon(vitalsUrl, body);
    } else {
      fetch(vitalsUrl, {
        body,
        method: 'POST',
        keepalive: true,
      });
    }
  }
  
  // Measure performance
  try {
    getFCP(sendToAnalytics);
    getLCP(sendToAnalytics);
    getCLS(sendToAnalytics);
    getFID(sendToAnalytics);
    getTTFB(sendToAnalytics);
  } catch (err) {
    console.error('Failed to measure web vitals:', err);
  }
}

// Custom error boundary
export function ProductionErrorBoundary({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <Sentry.ErrorBoundary
      fallback={({ error, resetError }) => (
        <div className="min-h-screen flex items-center justify-center">
          <div className="text-center">
            <h1 className="text-2xl font-bold mb-4">
              Something went wrong
            </h1>
            <p className="text-muted-foreground mb-4">
              We've been notified and are working on a fix.
            </p>
            <Button onClick={resetError}>Try again</Button>
          </div>
        </div>
      )}
      showDialog
    >
      {children}
    </Sentry.ErrorBoundary>
  );
}

7. Database Connection Pooling

Optimize database connections:

typescript
// lib/database/connection-pool.ts
import { createClient } from '@supabase/supabase-js';
import { Pool } from 'pg';

// Create connection pool for direct queries
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Monitor pool health
pool.on('error', (err) => {
  console.error('Unexpected error on idle client', err);
  Sentry.captureException(err);
});

// Supabase client with custom fetch
export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
  {
    global: {
      fetch: (url, options = {}) => {
        return fetch(url, {
          ...options,
          // Add timeout
          signal: AbortSignal.timeout(10000),
        });
      },
    },
    db: {
      schema: 'public',
    },
    auth: {
      persistSession: true,
      autoRefreshToken: true,
    },
  }
);

8. CDN Configuration

Set up asset optimization:

javascript
// next.config.js
module.exports = {
  images: {
    domains: ['your-cdn.com'],
    loader: 'cloudinary',
    path: 'https://res.cloudinary.com/your-cloud/',
    formats: ['image/avif', 'image/webp'],
    minimumCacheTTL: 31536000,
  },
  
  async rewrites() {
    return [
      {
        source: '/assets/:path*',
        destination: 'https://your-cdn.com/assets/:path*',
      },
    ];
  },
  
  // Asset optimization
  compress: true,
  poweredByHeader: false,
  generateEtags: true,
  
  // Production optimizations
  compiler: {
    removeConsole: {
      exclude: ['error', 'warn'],
    },
  },
};

9. Health Check Endpoint

Production health monitoring:

typescript
// app/api/health/route.ts
export async function GET() {
  const checks = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.NEXT_PUBLIC_APP_VERSION || 'unknown',
    checks: {} as Record<string, any>,
  };
  
  // Check critical services
  const healthChecks = [
    checkDatabase(),
    checkStripe(),
    checkEmail(),
    checkCache(),
  ];
  
  const results = await Promise.allSettled(healthChecks);
  
  results.forEach((result, index) => {
    const serviceName = ['database', 'stripe', 'email', 'cache'][index];
    
    if (result.status === 'fulfilled') {
      checks.checks[serviceName] = result.value;
    } else {
      checks.checks[serviceName] = {
        status: 'unhealthy',
        error: result.reason.message,
      };
      checks.status = 'degraded';
    }
  });
  
  return NextResponse.json(checks, {
    status: checks.status === 'healthy' ? 200 : 503,
    headers: {
      'Cache-Control': 'no-cache, no-store, must-revalidate',
    },
  });
}

10. Post-Deployment Checklist

Verify production deployment:

typescript
// scripts/post-deploy-check.ts
async function verifyDeployment(url: string) {
  const checks = [
    { name: 'Homepage loads', check: () => checkUrl(url) },
    { name: 'API health', check: () => checkUrl(`${url}/api/health`) },
    { name: 'Auth works', check: () => checkAuth(url) },
    { name: 'Stripe webhook', check: () => checkWebhook(url) },
    { name: 'SSL certificate', check: () => checkSSL(url) },
    { name: 'Security headers', check: () => checkHeaders(url) },
  ];
  
  console.log('🔍 Running post-deployment checks...\n');
  
  for (const { name, check } of checks) {
    try {
      await check();
      console.log(`✅ ${name}`);
    } catch (error) {
      console.error(`❌ ${name}: ${error.message}`);
      process.exit(1);
    }
  }
  
  console.log('\n🎉 All checks passed!');
}

Production Checklist

Before going live:

  • [ ] Environment variables configured
  • [ ] Database migrated and backed up
  • [ ] SSL certificate active
  • [ ] Security headers configured
  • [ ] Error tracking enabled
  • [ ] Performance monitoring active
  • [ ] Webhooks tested
  • [ ] Email sending verified
  • [ ] Payment processing tested
  • [ ] Backup strategy implemented
  • [ ] Incident response plan ready
  • [ ] Documentation updated

License Requirement

Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.

Built with ❤️ for the AI Coding community, by Praney Behl