Vercel
Deploy Launchy to Vercel with Postgres (Neon), Redis (Upstash), and Cloudflare R2.
Launchy deploys to Vercel with zero config. Below is the exact sequence for a production-ready setup.
1. Provision services
Postgres — Neon
- neon.tech → Create project
- Copy the Pooled connection string (for app runtime)
- Copy the Direct connection string (for migrations — no pooling)
DATABASE_URL=postgresql://user:[email protected]/neondb?sslmode=require
# For migrations only (optional but recommended on Neon):
DATABASE_DIRECT_URL=postgresql://user:[email protected]/neondb?sslmode=requireRedis — Upstash
- upstash.com → Create Redis database
- Pick the same region as your Vercel deploy
- Copy the TCP endpoint:
REDIS_HOST=eu1-xxx.upstash.io
REDIS_PORT=6379
REDIS_PASSWORD=...R2 — Cloudflare
See R2 Storage integration for the full setup. You'll end with:
R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=launchy-uploads
R2_PUBLIC_URL=https://cdn.your-domain.com
NEXT_PUBLIC_CDN_HOSTNAME=cdn.your-domain.comStripe, Plunk, Discord, Turnstile
All optional on first deploy. Add later from .env.example.
2. Deploy to Vercel
- Push your code to a private GitHub/GitLab/Bitbucket repo
- vercel.com/new → Import Git Repository → pick your repo
- Framework preset: Next.js (auto-detected)
- Root directory: leave default
- Environment Variables section: paste everything from your
.env(see Environment Variables for the full list)
Critical env vars that must be set:
DATABASE_URL=...
BETTER_AUTH_SECRET=<openssl rand -base64 32>
BETTER_AUTH_URL=https://your-domain.com
REDIS_HOST=...
REDIS_PASSWORD=...
CRON_SECRET=<openssl rand -base64 32>- Click Deploy
First deploy takes 2–3 minutes.
3. Run the migration
Vercel doesn't run migrations automatically. You need to push the schema once:
# From your local machine, with production DATABASE_URL:
DATABASE_URL="postgresql://...prod..." bun db:pushAlternatively, add a Build Command that runs migrations:
bun db:push && bun run buildBut be careful — db:push can truncate data if it detects a column rename as a drop. For production migrations, use versioned migrations:
# Generate a migration locally:
bun drizzle-kit generate
# Commit the generated SQL in drizzle/
# Deploy (Vercel runs it automatically if you use drizzle-kit migrate in the build command)4. Point your domain
- Vercel project → Settings → Domains → Add
- Enter
your-domain.com - Update your DNS registrar with the CNAME/A records Vercel provides
- Wait for propagation (5-60 min)
- TLS certificate issues automatically
Update BETTER_AUTH_URL to the final domain:
BETTER_AUTH_URL=https://your-domain.comRedeploy (Vercel → Deployments → latest → Redeploy) so the new env var takes effect.
5. Configure the cron
Launchy ships a vercel.json at the repo root with both crons registered:
{
"crons": [
{ "path": "/api/cron/weekly-batch", "schedule": "0 6 * * 1" },
{ "path": "/api/cron/cleanup-highlights", "schedule": "0 0 * * *" }
]
}Schedules are UTC. The two crons are:
weekly-batch— publishes the next batch + sends the newsletter. Default: Monday 06:00 UTC.cleanup-highlights— clearshighlight_expires_aton expired Highlight products. Default: daily at 00:00 UTC.
If you change your launch_day / launch_hour_utc settings in /admin/settings, you must also edit the weekly-batch schedule in vercel.json to match (e.g., 0 14 * * 3 for Wednesday 14:00 UTC) and redeploy.
Vercel Hobby tier allows up to 2 cron jobs — these two fit exactly.
Vercel's cron calls include an x-vercel-cron-signature header, which the routes accept in place of Authorization: Bearer $CRON_SECRET.
6. Set up Stripe webhook
See Stripe integration → production section. Webhook endpoint: https://your-domain.com/api/stripe/webhook.
7. First visit checks
- Homepage loads
- Sign up works, verification email arrives (if Plunk configured)
- Sign in works
-
/adminaccessible after SQL promote -
/admin/settingsloads and saves work - Trigger cron manually:
curl -X POST https://your-domain.com/api/cron/weekly-batch -H "Authorization: Bearer $CRON_SECRET" - No errors in Vercel → Functions → Logs
Preview deployments
Every Git branch pushed creates a Vercel preview URL. To prevent auth issues:
- Vercel → Settings → Environment Variables →
BETTER_AUTH_URL→ Set per environment:- Production →
https://your-domain.com - Preview → leave blank or use
VERCEL_URLcomputed at runtime
- Production →
Or skip Better Auth's URL validation in preview by setting NODE_ENV=development — but that's hacky.
Cleanest: don't rely on auth in previews. Use them for UI review only.
Monitoring
Vercel Dashboard shows:
- Functions — logs of every serverless invocation
- Analytics — Web Vitals (if enabled)
- Logs — real-time tail
Set up log drains to Axiom / Datadog / Logtail for longer retention.
Scaling beyond Vercel free tier
Vercel Pro ($20/mo) is plenty for most directories until 100k monthly visitors. Beyond that:
- Consider moving the DB to a paid Neon plan or a dedicated Postgres VPS
- Consider Vercel Enterprise or migrating to self-host
Launchy itself has no architectural scaling limits — Next.js on Vercel Edge/Node scales horizontally.
Common issues
"DATABASE_URL not set" — Variable missing or wrong scope (Production vs Preview). Double-check Vercel → Settings → Environment Variables.
OAuth redirect loop after deploy — BETTER_AUTH_URL must be the final production URL (not vercel.app). Update, redeploy.
Images not loading — NEXT_PUBLIC_CDN_HOSTNAME missing. Add, redeploy.
Cron never runs — Check vercel.json is in root and committed. Vercel → Crons tab shows scheduled jobs.