$npx -y skills add sabahattink/antigravity-fullstack-hq --skill deployment-guideVercel frontend + Railway/Fly.io backend deployment, env vars, health checks. Use when deploying a Next.js frontend to Vercel or a NestJS backend to Railway or Fly.io, or troubleshooting a deployment issue.
| 1 | # Deployment Guide |
| 2 | |
| 3 | ## Frontend — Vercel |
| 4 | |
| 5 | ### Setup |
| 6 | ```bash |
| 7 | # Install Vercel CLI |
| 8 | npm i -g vercel |
| 9 | |
| 10 | # Login |
| 11 | vercel login |
| 12 | |
| 13 | # Link project (run once in the project root) |
| 14 | vercel link |
| 15 | |
| 16 | # Deploy to preview |
| 17 | vercel |
| 18 | |
| 19 | # Deploy to production |
| 20 | vercel --prod |
| 21 | ``` |
| 22 | |
| 23 | ### vercel.json |
| 24 | ```json |
| 25 | { |
| 26 | "framework": "nextjs", |
| 27 | "buildCommand": "pnpm run build", |
| 28 | "installCommand": "pnpm install --frozen-lockfile", |
| 29 | "outputDirectory": ".next", |
| 30 | "regions": ["iad1"], |
| 31 | "headers": [ |
| 32 | { |
| 33 | "source": "/(.*)", |
| 34 | "headers": [ |
| 35 | { "key": "X-Content-Type-Options", "value": "nosniff" }, |
| 36 | { "key": "X-Frame-Options", "value": "DENY" }, |
| 37 | { "key": "X-XSS-Protection", "value": "1; mode=block" }, |
| 38 | { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" } |
| 39 | ] |
| 40 | }, |
| 41 | { |
| 42 | "source": "/api/(.*)", |
| 43 | "headers": [ |
| 44 | { "key": "Cache-Control", "value": "no-store, max-age=0" } |
| 45 | ] |
| 46 | }, |
| 47 | { |
| 48 | "source": "/_next/static/(.*)", |
| 49 | "headers": [ |
| 50 | { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } |
| 51 | ] |
| 52 | } |
| 53 | ], |
| 54 | "rewrites": [ |
| 55 | { |
| 56 | "source": "/api/:path*", |
| 57 | "destination": "https://api.example.com/api/:path*" |
| 58 | } |
| 59 | ] |
| 60 | } |
| 61 | ``` |
| 62 | |
| 63 | ### Vercel Environment Variables |
| 64 | ```bash |
| 65 | # Set env vars per environment |
| 66 | vercel env add NEXT_PUBLIC_API_URL production |
| 67 | vercel env add NEXT_PUBLIC_API_URL preview |
| 68 | vercel env add NEXT_PUBLIC_API_URL development |
| 69 | |
| 70 | # Pull to local .env.local |
| 71 | vercel env pull .env.local |
| 72 | ``` |
| 73 | |
| 74 | ### next.config.ts for Production |
| 75 | ```typescript |
| 76 | import type { NextConfig } from 'next' |
| 77 | |
| 78 | const config: NextConfig = { |
| 79 | output: 'standalone', |
| 80 | |
| 81 | // Strict mode catches side effects in development |
| 82 | reactStrictMode: true, |
| 83 | |
| 84 | // Image optimization |
| 85 | images: { |
| 86 | domains: ['cdn.example.com', 'avatars.githubusercontent.com'], |
| 87 | formats: ['image/avif', 'image/webp'], |
| 88 | }, |
| 89 | |
| 90 | // Bundle analyzer (install @next/bundle-analyzer) |
| 91 | // ...(process.env.ANALYZE === 'true' && require('@next/bundle-analyzer')()) |
| 92 | |
| 93 | // Security headers |
| 94 | async headers() { |
| 95 | return [ |
| 96 | { |
| 97 | source: '/(.*)', |
| 98 | headers: [ |
| 99 | { key: 'X-DNS-Prefetch-Control', value: 'on' }, |
| 100 | ], |
| 101 | }, |
| 102 | ] |
| 103 | }, |
| 104 | |
| 105 | // Redirects |
| 106 | async redirects() { |
| 107 | return [ |
| 108 | { source: '/old-path', destination: '/new-path', permanent: true }, |
| 109 | ] |
| 110 | }, |
| 111 | } |
| 112 | |
| 113 | export default config |
| 114 | ``` |
| 115 | |
| 116 | ## Backend — Railway |
| 117 | |
| 118 | ### Setup |
| 119 | ```bash |
| 120 | # Install Railway CLI |
| 121 | npm i -g @railway/cli |
| 122 | |
| 123 | # Login |
| 124 | railway login |
| 125 | |
| 126 | # Initialize (run in project root) |
| 127 | railway init |
| 128 | |
| 129 | # Deploy |
| 130 | railway up |
| 131 | |
| 132 | # Open dashboard |
| 133 | railway open |
| 134 | |
| 135 | # View logs |
| 136 | railway logs |
| 137 | |
| 138 | # Run commands in service |
| 139 | railway run pnpm run migration:run |
| 140 | ``` |
| 141 | |
| 142 | ### railway.json |
| 143 | ```json |
| 144 | { |
| 145 | "$schema": "https://railway.app/railway.schema.json", |
| 146 | "build": { |
| 147 | "builder": "DOCKERFILE", |
| 148 | "dockerfilePath": "apps/api/Dockerfile" |
| 149 | }, |
| 150 | "deploy": { |
| 151 | "startCommand": "node dist/main", |
| 152 | "healthcheckPath": "/health/liveness", |
| 153 | "healthcheckTimeout": 30, |
| 154 | "restartPolicyType": "ON_FAILURE", |
| 155 | "restartPolicyMaxRetries": 3 |
| 156 | } |
| 157 | } |
| 158 | ``` |
| 159 | |
| 160 | ### Required Environment Variables (Railway) |
| 161 | ```bash |
| 162 | # Set via Railway dashboard or CLI |
| 163 | DATABASE_URL = ${{Postgres.DATABASE_URL}} # Railway managed Postgres |
| 164 | REDIS_URL = ${{Redis.REDIS_URL}} # Railway managed Redis |
| 165 | NODE_ENV = production |
| 166 | PORT = ${{PORT}} # Railway injects this |
| 167 | JWT_SECRET = <generate: openssl rand -base64 32> |
| 168 | CORS_ORIGINS = https://your-app.vercel.app |
| 169 | LOG_LEVEL = info |
| 170 | ``` |
| 171 | |
| 172 | ## Backend — Fly.io (Alternative) |
| 173 | |
| 174 | ### Setup |
| 175 | ```bash |
| 176 | # Install flyctl |
| 177 | curl -L https://fly.io/install.sh | sh |
| 178 | |
| 179 | # Login |
| 180 | fly auth login |
| 181 | |
| 182 | # Launch app (creates fly.toml) |
| 183 | fly launch --name my-api --region iad --no-deploy |
| 184 | |
| 185 | # Deploy |
| 186 | fly deploy |
| 187 | |
| 188 | # Scale |
| 189 | fly scale count 2 |
| 190 | fly scale memory 512 |
| 191 | |
| 192 | # View logs |
| 193 | fly logs |
| 194 | |
| 195 | # SSH into instance |
| 196 | fly ssh console |
| 197 | ``` |
| 198 | |
| 199 | ### fly.toml |
| 200 | ```toml |
| 201 | app = "my-api" |
| 202 | primary_region = "iad" |
| 203 | |
| 204 | [build] |
| 205 | dockerfile = "apps/api/Dockerfile" |
| 206 | |
| 207 | [env] |
| 208 | NODE_ENV = "production" |
| 209 | PORT = "8080" |
| 210 | |
| 211 | [http_service] |
| 212 | internal_port = 8080 |
| 213 | force_https = true |
| 214 | auto_stop_machines = true |
| 215 | auto_start_machines = true |
| 216 | min_machines_running = 1 |
| 217 | |
| 218 | [http_service.concurrency] |
| 219 | type = "connections" |
| 220 | hard_limit = 200 |
| 221 | soft_limit = 150 |
| 222 | |
| 223 | [[vm]] |
| 224 | memory = "512mb" |
| 225 | cpu_kind = "shared" |
| 226 | cpus = 1 |
| 227 | |
| 228 | [checks] |
| 229 | [checks.health] |
| 230 | grace_period = "30s" |
| 231 | interval = "15s" |
| 232 | method = "GET" |
| 233 | path = "/health/liveness" |
| 234 | port = 8080 |
| 235 | timeout = "5s" |
| 236 | type = "http" |
| 237 | ``` |
| 238 | |
| 239 | ## Health Check Endpoints |
| 240 | |
| 241 | ```typescript |
| 242 | // health/health.controller.ts — must respond before traffic is routed |
| 243 | |
| 244 | @Controller('health') |
| 245 | export class HealthController { |