Production Readiness and Deployment for Makerkit Next.js Drizzle
Complete production deployment guide for your Next.js SaaS. Learn database migration strategies, authentication security hardening, Stripe live configuration, email deliverability optimization, and deployment with Vercel or Docker.
You've built TeamPulse from the ground up—database schema, authentication, billing, and a polished admin dashboard. Now it's time to take everything you've built and prepare it for real users. Production deployment introduces new challenges: secure credential management, reliable database migrations, and the operational concerns that only matter when actual customers depend on your application.
This module walks you through each production consideration systematically. Rather than a simple checklist, you'll understand why each configuration matters and what can go wrong if you skip it. By the end, you'll have confidence that TeamPulse is secure, monitored, and ready for launch.
What you'll accomplish:
- Understand production vs development differences
- Configure authentication, billing, and email for production
- Learn deployment options
- Complete a security and performance checklist
- Have a pre-launch checklist ready
Production Environment Overview
The development environment is designed for fast iteration—you can break things, reset databases, and experiment freely. Production is the opposite: every change must be deliberate, errors affect real users, and security is non-negotiable.
Development vs Production
The table below summarizes the key differences. Notice that production isn't just "development with a different URL"—each aspect requires its own configuration and careful handling:
| Aspect | Development | Production |
|---|---|---|
| URL | http://localhost:3000 | https://yourdomain.com |
| Database | Local Docker PostgreSQL | Managed PostgreSQL (SSL) |
| Mailpit (captured locally) | Resend or production SMTP | |
| Stripe | Test keys (pk_test_) | Live keys (pk_live_) |
| Errors | Verbose console output | Error tracking (Sentry) |
Environment Variable Categories
Next.js distinguishes between public and private environment variables. This distinction is crucial for security—leaking a secret key to the browser would expose your entire application.
Public variables are bundled into your client-side JavaScript. Anyone can see them in the browser's Network tab or by inspecting your JavaScript bundle. Only include values that are safe to expose publicly:
# Prefix with NEXT_PUBLIC_NEXT_PUBLIC_SITE_URL=https://teampulse.comNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...NEXT_PUBLIC_ACCOUNT_MODE=organizations-onlyPrivate variables remain on the server. Next.js deliberately excludes them from client bundles. Never prefix these with NEXT_PUBLIC_, and never log them or pass them to client components:
# No prefix - never sent to browserDATABASE_URL=postgresql://...BETTER_AUTH_SECRET=random-32-char-secretSTRIPE_SECRET_KEY=sk_live_...STRIPE_WEBHOOK_SECRET=whsec_...HTTPS Enforcement
HTTPS encrypts all traffic between users and your server, preventing attackers from intercepting session cookies, passwords, or sensitive data. Modern browsers increasingly restrict features (like geolocation and service workers) to HTTPS-only contexts, and search engines penalize HTTP sites in rankings.
Makerkit enforces HTTPS by validating your NEXT_PUBLIC_SITE_URL during the build process:
// apps/web/config/app.config.ts// Rejects http:// URLs in production buildsProduction deployments on Vercel or similar platforms provide free SSL certificates automatically. For self-hosted deployments, use a reverse proxy like Caddy (which handles certificates automatically) or configure Let's Encrypt with nginx.
Database for Production
Your local Docker PostgreSQL was perfect for development, but production requires a managed database service. Managed providers handle backups, security patches, high availability, and scaling—tasks that would otherwise consume significant time and expertise.
Managed PostgreSQL Providers
Each provider has different strengths. Choose based on your team's needs and budget:
| Provider | Best For | Notes |
|---|---|---|
| Supabase | Quick setup | Built on PostgreSQL, includes auth (not used) |
| Neon | Serverless | Auto-scaling, branching for dev |
| AWS RDS | Enterprise | Full control, more setup |
| PlanetScale | Not supported | MySQL only |
Supabase offers the gentlest learning curve since it includes a dashboard, built-in connection pooling, and familiar PostgreSQL tooling. The included auth system isn't used (Better Auth handles authentication), but the database itself is production-ready.
Neon scales to zero when idle, making it cost-effective for applications with variable traffic. Database branching lets you create isolated copies for testing migrations before applying them to production.
AWS RDS provides maximum control for teams with DevOps experience. You manage more infrastructure details, but gain fine-grained control over instance types, replication, and network configuration.
Migration Strategy
Database migrations in production require a different approach than development. In development, you can wipe and recreate the database freely. In production, you're managing real customer data that can never be lost.
We do not recommend using Drizzle's push command, but to always generate migration files and apply them before deploying to production.
Each migration is a versioned SQL file that can be reviewed, tested, and rolled back if needed. This deliberate process protects against accidental data loss:
# 1. Generate migration filespnpm --filter @kit/database drizzle:generate# 2. Review generated SQL in /packages/database/src/schema/# 3. Run migrations before deploymentpnpm --filter @kit/database drizzle:migrateConnection Configuration
Production databases require SSL encryption to protect data in transit. The sslmode=require parameter ensures connections are encrypted—without it, credentials and queries would be transmitted in plaintext over the network:
# Production DATABASE_URL with SSLDATABASE_URL=postgresql://user:password@host:5432/database?sslmode=requireConnection pooling is built into the postgres-js client. Pooling reuses database connections across requests rather than creating new connections for each query—critical for serverless environments where cold starts are frequent:
- Max connections: 10
- Idle timeout: 20 seconds
- Connect timeout: 10 seconds
Migration Best Practices
Each of these practices protects your production data from common migration failures:
Add nullable columns — Adding a required column without a default value fails immediately on tables with existing rows. Either make new columns nullable, provide a default value, or use a multi-step migration.
Test on staging first — Run every migration against a copy of production data before applying to production. This catches issues with existing data that unit tests miss.
Backup before migrating — Even with careful testing, migrations can fail partway through. Point-in-time recovery lets you restore to moments before the migration started.
Run migrations before deployment — If migrations run during application startup and fail, your new code deploys without the database changes it expects. Run migrations as a separate CI/CD step that must succeed before deployment proceeds.
Authentication in Production
Authentication is the most security-critical component of your application. A compromised auth system exposes all user data and allows attackers to impersonate any user. Better Auth handles most security concerns automatically, but you must configure it correctly.
Required Configuration
The BETTER_AUTH_SECRET cryptographically signs session tokens. If an attacker discovers this secret, they can forge valid sessions for any user. Generate a strong random value and never commit it to version control:
# Random 32+ character secret (generate with: openssl rand -hex 32)BETTER_AUTH_SECRET=your-random-secret-at-least-32-characters# Your production URL (HTTPS required)NEXT_PUBLIC_SITE_URL=https://teampulse.comNote: Better Auth uses
NEXT_PUBLIC_SITE_URLfor CORS and origin validation automatically.
Session Security
Better Auth configures secure cookie settings automatically in production. Understanding these settings helps you audit your security posture:
- secure: true — Cookies transmit only over HTTPS, preventing interception on insecure networks
- httpOnly: true — JavaScript cannot access the cookie, blocking XSS attacks from stealing sessions
- sameSite: lax — Cookies aren't sent with cross-site requests, preventing CSRF attacks
OAuth Redirect URLs
OAuth providers validate redirect URLs to prevent authorization code interception attacks. You must register your production URLs in each provider's dashboard—attempts to redirect elsewhere will fail:
Google Cloud Console:
Authorized redirect URIs:https://teampulse.com/api/auth/callback/googleGitHub Developer Settings:
Authorization callback URL:https://teampulse.com/api/auth/callback/githubOAuth Provider Configuration
Create separate OAuth applications for production—don't reuse development credentials. Production credentials should have stricter redirect URL restrictions and be stored only in your production environment:
# Production OAuth credentialsGOOGLE_CLIENT_ID=your-production-client-idGOOGLE_CLIENT_SECRET=your-production-client-secret# Add other providers as neededGITHUB_CLIENT_ID=...GITHUB_CLIENT_SECRET=...Billing/Stripe Production Setup
Stripe maintains completely separate environments for testing and production. Test mode charges don't process real payments—you can experiment freely without affecting real money. Production mode processes real charges against real credit cards.
Test vs Live Keys
The key prefix tells you immediately which environment you're using. Mixing keys (e.g., live publishable with test secret) causes authentication failures:
| Key Type | Prefix | Use |
|---|---|---|
| Test Publishable | pk_test_ | Development |
| Test Secret | sk_test_ | Development |
| Live Publishable | pk_live_ | Production |
| Live Secret | sk_live_ | Production |
Production Configuration
You'll need to create new products and prices in Stripe's live mode—test mode products don't exist in production. The price IDs will be different, so update all environment variables:
# Stripe live keysNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...STRIPE_SECRET_KEY=sk_live_...# Production webhook secret (different from test)STRIPE_WEBHOOK_SECRET=whsec_...# Price IDs (create new products for production)STRIPE_PRICE_PRO_MONTHLY=price_live_...STRIPE_PRICE_PRO_YEARLY=price_live_...Production Webhook Setup
Webhooks allow Stripe to notify your application about payment events—subscription created, payment failed, trial ending, etc. Without webhooks, your application won't know when customers successfully complete checkout or when their subscriptions change.
Create a production webhook endpoint (separate from your test endpoint):
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Enter URL:
https://teampulse.com/api/auth/stripe/webhook - Select events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_failedcustomer.subscription.trial_will_end
- Copy the signing secret to
STRIPE_WEBHOOK_SECRET
Stripe Checklist
Before launching, verify every component of the billing flow works with live credentials:
- [ ] Create production products and prices in Stripe Dashboard
- [ ] Update price IDs in environment variables
- [ ] Configure production webhook endpoint
- [ ] Test checkout flow with live keys (use real card for $1 test)
- [ ] Verify webhook events are received
- [ ] Set up failed payment email notifications in Stripe
Email in Production
Email deliverability is often underestimated. Sending from an unverified domain, using generic provider domains, or missing authentication records will land your emails in spam folders—or block them entirely. Proper email configuration directly impacts user experience and conversion rates.
Provider Options
Choose a provider based on your volume and integration preferences. Both options below deliver reliably when configured correctly:
Option 1: Resend (Recommended) — Modern API, excellent developer experience, built-in analytics:
MAILER_PROVIDER=resendRESEND_API_KEY=re_live_...EMAIL_SENDER=TeamPulse <noreply@teampulse.com>Option 2: SMTP (SendGrid, AWS SES, etc.) — Traditional approach, works with any SMTP-compatible provider:
MAILER_PROVIDER=nodemailerEMAIL_HOST=smtp.sendgrid.netEMAIL_PORT=587EMAIL_TLS=trueEMAIL_USER=apikeyEMAIL_PASSWORD=SG.your-sendgrid-api-keyEMAIL_SENDER=TeamPulse <noreply@teampulse.com>Domain Verification
Email providers and recipients verify sender authenticity through DNS records. Without proper verification, your emails may be rejected or marked as spam. These records prove you control the domain you're sending from:
Resend:
- Add domain in Resend dashboard
- Add DNS records (SPF, DKIM, DMARC)
- Wait for verification
SMTP providers:
- Verify domain in provider dashboard
- Configure SPF record:
v=spf1 include:sendgrid.net ~all - Set up DKIM signing
- Consider DMARC policy
SPF (Sender Policy Framework) lists which servers can send email for your domain. DKIM (DomainKeys Identified Mail) cryptographically signs messages to prove they haven't been tampered with. DMARC tells receiving servers what to do when SPF or DKIM checks fail.
Email Checklist
Test every email your application sends before launching—broken password reset emails lock users out of their accounts:
- [ ] Choose email provider (Resend recommended)
- [ ] Verify sending domain
- [ ] Update EMAIL_SENDER with your domain
- [ ] Test all email types (verification, password reset, invitations)
- [ ] Check emails don't land in spam
Deployment Options
Your deployment choice affects operational complexity, cost, and scaling behavior. Vercel optimizes specifically for Next.js and handles most operational concerns automatically. Docker provides flexibility for teams with existing infrastructure or specific compliance requirements.
Option 1: Vercel (Recommended)
Vercel provides the easiest deployment for Next.js applications. The platform auto-detects Next.js projects, optimizes builds, and handles edge functions, image optimization, and global CDN distribution without configuration:
Setup:
- Connect GitHub repository to Vercel
- Configure build settings:
- Framework: Next.js (auto-detected)
- Build Command:
pnpm build - Install Command:
pnpm install
- Add environment variables in Vercel Dashboard
- Deploy
Custom Domain:
- Add domain in Vercel project settings
- Update DNS records as instructed
- SSL is automatic
Environment Variables:
- Add all production variables in Vercel Dashboard
- Use different values per environment (Preview, Production)
Option 2: Docker
For self-hosted deployments on AWS, GCP, DigitalOcean, or your own servers, Makerkit provides a Docker generator that handles the monorepo structure correctly:
# Generate Dockerfile and enable standalone outputpnpm turbo gen dockerThis generator:
- Adds
output: "standalone"to next.config.ts - Creates a production-ready Dockerfile for the monorepo
- Handles all monorepo-specific paths correctly
Build and run the container:
docker build -t teampulse .docker run -p 3000:3000 --env-file .env.production teampulseNote: Don't copy a Dockerfile from tutorials—the monorepo structure requires specific paths. Always use the generator.
Deployment Checklist
Regardless of platform, verify these items before considering deployment complete:
- [ ] Choose deployment platform
- [ ] Configure all environment variables
- [ ] Set up custom domain with SSL
- [ ] Run database migrations before deployment
- [ ] Verify all systems work after deployment
Security Checklist
Security isn't a feature you add at the end—it's a property of how your application is built. This checklist helps you verify that the security measures built into Makerkit and TeamPulse are properly configured for production.
Authentication Security
These items protect against session hijacking, credential theft, and authentication bypass:
- [ ] Strong secret: BETTER_AUTH_SECRET is 32+ random characters — A weak secret lets attackers forge valid session tokens
- [ ] HTTPS only: NEXT_PUBLIC_SITE_URL uses https:// — HTTP exposes session cookies to network interception
- [ ] Trusted origins: BETTER_AUTH_TRUSTED_ORIGINS is set — Prevents cross-origin authentication attacks
- [ ] OAuth configured: Redirect URLs match production domain — Mismatched URLs cause login failures or enable redirect attacks
Data Security
These protections guard against the most common web vulnerabilities:
- [ ] Input validation: All forms use Zod schemas — Validates data on the server before processing
- [ ] SQL injection: Using Drizzle ORM (parameterized queries) — Query parameters are automatically escaped
- [ ] XSS prevention: React auto-escapes output — User content is rendered as text, not executable HTML
- [ ] CSRF protection: Session cookies use sameSite=lax — Browsers won't send cookies with cross-site form submissions
Access Control
Multi-tenant applications must enforce strict data isolation:
- [ ] Multi-tenancy: All queries filter by organizationId — Users see only their organization's data
- [ ] Role checks: Permissions verified server-side — Client-side checks can be bypassed
- [ ] Admin protection: Admin routes require admin role — Super admin functions require explicit role verification
Environment Security
Credential management prevents the most damaging breaches:
- [ ] No secrets in code: All secrets in environment variables — Code is often committed to version control or exposed in builds
- [ ] No secrets in logs: Sensitive data not logged — Logs are often less protected than databases
- [ ] Separate keys: Different API keys for dev/production — Compromised dev keys don't affect production
Database Security
Your database contains your most valuable asset—customer data:
- [ ] SSL required: DATABASE_URL includes ?sslmode=require — Encrypts database traffic against network interception
- [ ] Backups enabled: Provider has automated backups — Point-in-time recovery protects against data loss and ransomware
- [ ] Minimal permissions: DB user has only required permissions — Limits damage if credentials are compromised
Monitoring & Error Tracking
In development, you see errors immediately in your terminal. In production, errors happen silently—users experience problems but you don't know unless they report it (and most won't). Proper monitoring transforms production from a black box into an observable system.
Sentry Integration
Sentry captures errors with full context: stack traces, user information, request details, and breadcrumbs showing what happened before the error. This context is invaluable for debugging issues you can't reproduce locally:
# Sentry DSNNEXT_PUBLIC_SENTRY_DSN=https://xxx@sentry.io/xxxSENTRY_DSN=https://xxx@sentry.io/xxxSentry automatically captures:
- JavaScript errors in browser
- Server-side errors in API routes
- Unhandled promise rejections
Uptime Monitoring
External uptime monitors check your application from outside your infrastructure—they'll catch outages that internal monitoring might miss. Configure alerts via SMS, email, or Slack so you know immediately when users can't reach your application:
- BetterStack (formerly BetterUptime) — Modern interface, incident management
- UptimeRobot — Free tier available, simple setup
- Pingdom — Enterprise-grade, detailed analytics
Configure to check /api/healthcheck every 5 minutes.
Logging Strategy
Effective logging helps you debug production issues without access to the running system. Structure your logging to make searching and filtering easy:
- Use structured logging (JSON format) — Enables searching and filtering in log aggregators
- Don't log sensitive data (passwords, tokens) — Logs are often less protected than databases
- Log important business events (signups, subscriptions) — Helps debug user-reported issues
- Set appropriate log levels — Info in production, debug in development; verbose logging impacts performance
Monitoring Checklist
- [ ] Error tracking configured (Sentry recommended)
- [ ] Health check endpoint created
- [ ] Uptime monitoring set up
- [ ] Important events are logged
- [ ] Alerts configured for critical errors
Performance Checklist
Performance directly impacts user experience and conversion rates. Slow applications lose users—studies consistently show that page load time correlates with bounce rate. These optimizations ensure TeamPulse remains responsive as usage grows.
Image Optimization
Images often account for the majority of page weight. Next.js Image component automatically serves appropriately sized images and modern formats:
- [ ] Using Next.js Image component for images
- [ ] Images have appropriate quality setting (80 recommended)
- [ ] Large images are properly sized
Database Performance
Database queries are frequently the bottleneck. Index frequently-filtered columns and avoid fetching data you won't display:
- [ ] Indexes added on frequently queried columns
- [ ] Queries fetch only needed columns
- [ ] N+1 queries avoided (use Drizzle relations)
- [ ] Connection pooling configured
Bundle Optimization
Large JavaScript bundles increase time-to-interactive. Analyze your bundle and eliminate unnecessary code:
- [ ] Analyze bundle:
pnpm --filter web analyze - [ ] Remove unused dependencies
- [ ] Dynamic imports for heavy components
- [ ] Tree shaking working (import only what you need)
Caching
Strategic caching reduces server load and improves response times. Cache data that changes infrequently and pre-render static pages:
- [ ] Static pages use ISR where appropriate
- [ ] API responses cached where possible
- [ ] Client-side data uses SWR or TanStack Query
Pre-Launch Checklist
This comprehensive checklist consolidates everything you've configured. Work through each section systematically before announcing your launch. A missing checkbox here is a production issue waiting to happen.
Infrastructure
- [ ] Production database provisioned
- [ ] Database migrations run
- [ ] Deployment platform configured
- [ ] Custom domain with SSL
- [ ] Environment variables set
Authentication
- [ ] BETTER_AUTH_SECRET set (32+ chars)
- [ ] OAuth providers configured for production
- [ ] Email verification working
- [ ] Password reset working
Billing
- [ ] Stripe live keys configured
- [ ] Production webhook endpoint set up
- [ ] All price IDs updated
- [ ] Test purchase completed
- [ ] Webhook events verified
- [ ] Production email provider configured
- [ ] Sending domain verified
- [ ] All email templates tested
- [ ] Emails not going to spam
Security
- [ ] HTTPS enforced
- [ ] Secrets not in code
- [ ] Database SSL enabled
- [ ] Security headers configured
Monitoring
- [ ] Error tracking enabled
- [ ] Uptime monitoring configured
- [ ] Health check responding
- [ ] Alerts set up
Testing
- [ ] All critical flows tested manually
- [ ] E2E tests passing
- [ ] Mobile responsive verified
- [ ] Different browsers tested
Legal & Compliance
- [ ] Privacy policy page
- [ ] Terms of service page
- [ ] Cookie consent (if required)
- [ ] GDPR compliance (if applicable)
Module 12 Complete!
You now have:
- [x] Understanding of production vs development configuration
- [x] Knowledge of database, auth, billing, and email production setup
- [x] Awareness of deployment options (Vercel, Docker)
- [x] Security checklist for launch
- [x] Performance optimization checklist
- [x] Complete pre-launch checklist
Congratulations! You've completed the course and built TeamPulse—a fully-featured B2B SaaS application with:
- Feedback boards with CRUD operations
- Feedback items with voting and status workflow
- Public boards for anonymous submissions
- Google OAuth authentication
- Role-based permissions (Owner/Admin/Member)
- Plan limits for boards and feedback
- Custom email notifications
- Admin dashboard for platform management
- Production-ready configuration
More importantly, you've learned the patterns and practices that make Makerkit effective: loaders for data fetching, server actions for mutations, Zod for validation, and the multi-tenant architecture that keeps customer data isolated. These patterns will serve you as you extend TeamPulse or build entirely new applications.
What's next? Launch TeamPulse for real users, or use what you've learned to build your own SaaS product. The foundation you've built is production-ready—everything else is iteration based on user feedback.
Good luck with your launch!