ShipAgent

Deploy to Vercel

What the agent does and what you do to put the site on Vercel, why vercel.json has to go first, and what to do when a deploy fails.

Outcome: the health check answers 200 with "status":"ok" and "database":{"status":"ok"…}, first on the *.vercel.app production address, then on your domain:

curl -sS -w '\nHTTP %{http_code}\n' https://<project>.vercel.app/api/health
curl -sS -w '\nHTTP %{http_code}\n' https://<your-domain>/api/health

The agent does not report the deploy as done until it does.

Time: about 15 minutes of agent time plus one Vercel build. You connect GitHub to Vercel once and add the domain's DNS records.

Tell the agent: "Deploy to Vercel." It follows skills/deploy-vercel/SKILL.md.

First: vercel.json turns off deploys from main

The repo ships with this vercel.json:

{ "git": { "deploymentEnabled": { "main": false } } }

It exists because the default deploy target is Cloudflare. With it in place, a Vercel project imported from this repo never deploys a push to main. The agent deletes the file as its first step (or replaces its content with a crons block if you need the scheduled job, see below). If you deploy only to Vercel, it also turns off the Cloudflare workflow (gh workflow disable cloudflare.yml), which would otherwise try to build every day. gh workflow enable turns it back on.

The Cloudflare files (cloudflare/, wrangler*.jsonc, open-next.config.ts) stay in the repo and do nothing on Vercel.

What you do

You: a Vercel account. A site that takes payments needs the Pro plan; Hobby is for non-commercial use (check Vercel's pricing page).

You: let Vercel read the repository. vercel.com → Add New… → Project → Import Git Repository → Install or Adjust GitHub App permissions → allow the repo.

You: if the agent uses the Vercel CLI, confirm vercel login in the browser once.

You: a domain, and the DNS records Vercel shows for it (step 7 below). See Domain and DNS.

The agent asks for each of these when it reaches the step that needs it. It never prints a key back to you.

What the agent does

  1. Lets Vercel deploy main. Agent: deletes vercel.json, commits, pushes.
  2. Creates the project. Agent: vercel link and vercel git connect, or you import the repo in the dashboard (Add New… → Project → the repo → Import). Settings: Framework Preset Next.js, Root Directory ./, Build Command pnpm build (never pnpm cf:build), Install Command pnpm install. Node.js Version 22.x under Project → Settings → Build and Deployment. The agent adds ENABLE_EXPERIMENTAL_COREPACK=1 so Vercel installs the pnpm version the repo pins.
  3. Adds the environment variables. Agent: creates .env.production.local (never committed) from .env.local with the production values: NEXT_PUBLIC_SITE_URL (the vercel.app address until the domain is attached), DATABASE_URL (the production database), a fresh BETTER_AUTH_SECRET and CRON_SECRET. It adds every key to the Production environment with vercel env add, leaving out the Cloudflare-only keys (CF_ENV_KEY, CF_ENV_SALT, CLOUDFLARE_API_TOKEN, DEPLOYMENT_PLATFORM, WORKER_VERSION_ID, VERCEL_ENV). Then it runs the migrations on the production database.
  4. Deploys and verifies. Agent: pushes to main (or vercel deploy --prod), waits for the deployment to be Ready, runs the health check.
  5. Production Stripe webhook. Agent: registers the webhook for the production address, adds its secret to Vercel, redeploys, and fires one signed test event.
  6. Scheduled job (optional). Agent: adds /api/cron/credits to vercel.json once your site sells subscriptions or uses PayPal:
    { "crons": [{ "path": "/api/cron/credits", "schedule": "* * * * *" }] }
    The route is written to run every minute. If your plan does not allow per-minute crons, it can run less often (check Vercel's cron limits): due credit grants are also settled when a user's balance is read.
  7. Your domain. You: Project → Settings → Domains → Add → your domain. Vercel shows the records to create, usually an A record for the bare domain and a CNAME for www. Add them at your DNS provider and wait until Vercel shows the domain as valid. Agent: sets NEXT_PUBLIC_SITE_URL to the domain, registers a Stripe webhook for it, asks you to add the domain's callback URLs at each login provider you use, redeploys, and runs the health check on the domain.
  8. What is left. Agent: tells you what remains before launch: live Stripe keys, an env update and a redeploy.

If you add variables in the dashboard instead: Project → Settings → Environment Variables. You can paste the whole .env file into the Key field and Vercel splits it (check the dashboard, menus move). Choose the Production environment.

Vercel project settings, Environment Variables

Environment changes reach only new deployments. After changing a variable, redeploy: Deployments → the latest production deployment → … → Redeploy, or ask the agent to run vercel deploy --prod.

When a push goes live

After vercel.json is deleted, every push to main builds and deploys. Vercel offers version skew protection, which keeps a page left open across a deploy talking to the build it came from. It is a project setting that depends on your plan (check the dashboard, menus move).

Cloudflare or Vercel

The comparison table is on Cloudflare Workers. Two Vercel-specific points: image optimization (/_next/image) works and follows NEXT_PUBLIC_OPTIMIZED_IMAGES, and Vercel Analytics shows only on Vercel (the layout hides it when DEPLOYMENT_PLATFORM is set, so that variable must not be in the Vercel project).

When it fails

What you see Cause Fix
Pushes to main create no deployment vercel.json still disables main, or the repo is not connected the agent deletes the file or runs vercel git connect
Install fails with a pnpm or lockfile error Vercel picked another pnpm version the agent adds ENABLE_EXPERIMENTAL_COREPACK=1
Build fails reading the database, or DATABASE_URL is not set variables were added after the build started, or only to Preview add them to Production and redeploy
Health 200 but database: skipped DATABASE_URL missing in Production add it, redeploy
Health 503, database: down wrong DATABASE_URL, or the database refuses Vercel's connections the agent checks the value from your machine and fixes it in Vercel
Sign-in fails on the production address NEXT_PUBLIC_SITE_URL is another address the agent sets it to the address in use and redeploys
Stripe test event answers 400 Webhook Error the webhook secret in Vercel belongs to another endpoint the agent registers again and redeploys
The scheduled job runs but settles nothing CRON_SECRET is not set in Vercel add it, redeploy
curl gets a Vercel login page or 401 Deployment Protection is on for that URL You: test on the production domain, or turn protection off for production under Project → Settings → Deployment Protection
The Cloudflare workflow still runs every day it is independent of Vercel the agent runs gh workflow disable cloudflare.yml
The domain stays invalid in Vercel the DNS records are missing or wrong You: compare each record with what Vercel shows; DNS can take minutes to hours