Deployvercelneonstripedeploy

Deploy a Next.js SaaS kit: Vercel, Neon, Stripe, and S3

A production walkthrough for taking a SaaS starter from clone to first paid webhook - Neon migrations, auth secrets, Stripe fulfillment, private storage, and a Vercel domain that matches your public URL.

Twenty · July 19, 2026 · 4 min read

Deploy a Next.js SaaS kit: Vercel, Neon, Stripe, and S3

What "deployed" actually means

A green Vercel build is not a launched SaaS. Deployed means a stranger can sign up, complete Checkout in test mode, trigger a webhook that writes an entitlement, and reach a protected download or dashboard without you SSHing anywhere.

Most kit launches fail in the boring middle: migrations never ran against the production database, `NEXT_PUBLIC_APP_URL` still points at localhost, or Stripe Checkout succeeds while fulfillment silently never fires.

Treat deploy as a rehearsal. The first production push should feel dull because you already proved the path on a preview URL with test Stripe keys.

Prove the kit before you touch DNS

Clone or unzip the kit and make local reality match the seller's README.

cp .env.example .env.local
npm install
npx prisma generate
npm run build

If `build` fails on your laptop, production will fail with a worse stack trace. Fix TypeScript, missing peer deps, and Prisma generate issues first. Only then create cloud resources.

Skim the setup instructions for anything that assumes Docker, a second package manager, or a custom Node version. Those notes save an hour of "works on my machine" theater.

Neon: database before traffic

Create a Neon project and copy a connection string that includes `sslmode=require`. Put it in `DATABASE_URL`. Run migrations against that database before the first production deploy that serves users:

export DATABASE_URL="postgresql://USER:PASSWORD@ep-xxxx.aws.neon.tech/neondb?sslmode=require"
npx prisma migrate deploy

Using `db push` in production is a last resort for throwaway previews, not a habit. Prefer the migration history the kit already ships.

If the kit uses a pooled connection string for serverless and a direct URL for migrations, follow that split exactly. Mixing them is a common source of mysterious migrate failures.

Auth secrets that survive a reopen

Generate a long random `JWT_SECRET` (`openssl rand -base64 48` is fine). Never reuse the local secret in production. Set `NEXT_PUBLIC_APP_URL` to the final public origin people will type in a browser - for Twenty that is `https://twentysite.com` in production, not a `*.vercel.app` preview host.

If Open Graph images, OAuth callbacks, or password-reset emails point at the wrong host, users experience it as "the product is broken," not "my env is wrong."

Rotate secrets only with a plan. Changing `JWT_SECRET` without clearing sessions logs everyone out at once - sometimes that is what you want during an incident, sometimes it is an accidental outage.

Stripe: Checkout is half the story

Create a Stripe account and start in test mode. Copy the secret key into `STRIPE_SECRET_KEY`. Create a webhook endpoint aimed at your production path - commonly `/api/stripe/webhook` - and subscribe to at least `checkout.session.completed` (plus refund events if the kit listens for them).

Store `STRIPE_WEBHOOK_SECRET` from that endpoint. The classic launch bug is a successful payment page with no library unlock, because the webhook never reached your app or failed signature verification.

Locally you can forward events with the Stripe CLI. In production, confirm recent deliveries show `2xx` in the Stripe dashboard after a test purchase.

Do one full test purchase end to end before you switch to live keys. Then do another with live keys at a tiny amount if your risk tolerance allows. Webhook code paths that only run in production will surprise you otherwise.

Private object storage

If the kit stores ZIPs, screenshots, or user uploads, create a private S3 or Cloudflare R2 bucket. Keep access keys server-side. Prefer signed URLs or authenticated download routes. A public bucket full of paid kit archives is a support incident waiting to happen.

R2 needs an S3-compatible endpoint URL in addition to the key pair. AWS usually needs a region. Match the variable names in the kit's `.env.example` rather than inventing your own.

Upload a tiny file through the app once. Confirm it lands in the bucket and that a non-authenticated URL does not serve it.

Vercel last, then smoke test

Import the Git repo, paste every env var, deploy, and only then attach the custom domain. Walk the path a buyer will walk: sign up, verify email if required, complete a test Checkout, wait for the webhook, open Purchases or the library, and download or open the protected resource.

Printable commands for each stage live in Twenty's free deploy checklist. Pair it with the .env checklist generator so optional SMTP and OAuth keys do not surprise you on day two.

Related posts

Deploy Next.js SaaS on Vercel + Neon + Stripe | Twenty Blog