Skip to content

Module 10: Hosting & Deployment

Deploy your SaaS reliably and cost-effectively.

Platform Comparison

PlatformBest ForPricingCold Start
VercelNext.js appsFree tier, then usageFast
RailwayFull-stack + DB$5/mo + usageMedium
Fly.ioGlobal edge, containersPay per useFast
AWS/GCPEnterprise, custom needsComplexVaries

Deploy from Git

  1. Push code to GitHub
  2. Import project at vercel.com
  3. Configure environment variables
  4. Deploy

vercel.json Configuration

json
{
  "framework": "nextjs",
  "regions": ["iad1"],
  "env": {
    "DATABASE_URL": "@database-url"
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "no-store" }
      ]
    }
  ]
}

Environment Variables

bash
# Set via CLI
vercel env add DATABASE_URL production

# Or in dashboard: Settings → Environment Variables

# Access in code
const dbUrl = process.env.DATABASE_URL

Preview Deployments

Every PR gets a preview URL:

https://my-app-git-feature-branch-username.vercel.app

Custom Domain

  1. Add domain in Vercel dashboard
  2. Update DNS records:
    • A record: 76.76.21.21
    • CNAME: cname.vercel-dns.com

Railway

Good for apps needing persistent services (databases, Redis).

Deploy

bash
# Install CLI
npm install -g @railway/cli

# Login
railway login

# Init project
railway init

# Deploy
railway up

Database Setup

bash
# Add PostgreSQL
railway add

# Get connection string
railway variables

# Use in .env
DATABASE_URL=postgresql://...

railway.json

json
{
  "build": {
    "builder": "nixpacks"
  },
  "deploy": {
    "startCommand": "npm start",
    "healthcheckPath": "/api/health"
  }
}

Docker Deployment

Dockerfile

dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

next.config.js for Docker

javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig

CI/CD with GitHub Actions

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

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

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

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

      - run: npm ci
      - run: npm run lint
      - run: npm run test
      - run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

Database Migrations in CI

yaml
# In deploy job
- name: Run migrations
  run: npx prisma migrate deploy
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}

Health Checks

typescript
// app/api/health/route.ts
import { db } from '@/lib/db'

export async function GET() {
  try {
    // Check database
    await db.$queryRaw`SELECT 1`

    return Response.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
    })
  } catch (error) {
    return Response.json(
      { status: 'unhealthy', error: 'Database connection failed' },
      { status: 503 }
    )
  }
}

Monitoring

Vercel Analytics

typescript
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
        <SpeedInsights />
      </body>
    </html>
  )
}

Error Tracking (Sentry)

bash
npx @sentry/wizard@latest -i nextjs
typescript
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
})

Summary

  1. Vercel - Best for Next.js, easiest deployment
  2. Railway - Good all-in-one with databases
  3. Docker - For custom deployment needs
  4. CI/CD - Automate testing and deployment
  5. Monitoring - Track errors and performance

Next: Module 11: Background Jobs →