SaaS

Building a SaaS with Next.js, Stripe, and PostgreSQL

How to structure a Next.js 15 SaaS with Prisma, PostgreSQL, email-password sessions, and Stripe subscription state.

3 min read

A SaaS on Next.js is a small set of boundaries that have to agree: who the user is, which plan they are on, and what Stripe last told you. The Complete SaaS Starter Kit uses Next.js 15, React 19, Prisma 6, PostgreSQL, and the Stripe Node SDK 17. This guide is the shape of that project, not a second product.

What belongs in the first version

Ship accounts, a plan column, and a webhook before you invent features. The kit’s Prisma schema is intentionally small:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id                   String   @id @default(cuid())
  email                String   @unique
  passwordHash         String
  name                 String
  plan                 String   @default("free")
  stripeCustomerId     String?  @unique
  stripeSubscriptionId String?
  createdAt            DateTime @default(now())
}

plan is the field your pages read. Stripe remains the source of truth for money; the database is the copy your app can query without calling Stripe on every request. How that copy stays current is covered in synchronizing Stripe subscription state.

Request path

  1. A server action or route handler reads the session and loads User.
  2. Public pages (home, pricing, login) do not require a session.
  3. /dashboard redirects to login when currentUser() is empty.
  4. Paid screens check user.plan === "pro". They do not call Stripe.
  5. Checkout and the webhook are the only places that write stripeCustomerId, stripeSubscriptionId, and plan.

The Complete kit stores passwords with bcryptjs and sessions with jose. Both are already in its package.json. Do not put the Stripe secret or AUTH_SECRET in a NEXT_PUBLIC_ variable.

PostgreSQL locally

The kit’s README expects Docker Compose for Postgres and npx prisma db push against DATABASE_URL. Prisma 6 reads the URL from the schema env("DATABASE_URL"). Use a normal Postgres URL, not the SQLite file the smaller kits use. The Next.js SaaS Boilerplate defaults to SQLite so you can boot without Docker; this kit does not.

Billing is a separate module

Keep Checkout, the Customer Portal, and the webhook in src/app/api/stripe/. The rest of the app only sees plan. That split is why the same billing code also exists as the Stripe Subscription Starter, which uses SQLite instead of Postgres. Read Stripe subscriptions with Next.js before you copy a Checkout session into a page component.

What this does not decide

The schema has no teams table, no seat count, and no invoice history. A plan string of free or pro is enough for the kit’s route guard. Add organizations when a customer can belong to more than one account; until then a user row is the tenant.

Want to skip the setup? The Complete SaaS Starter Kit already includes the Postgres schema, session auth, Checkout, Customer Portal, webhooks, and a dashboard.

Product updates for developers shipping with Designyff kits.

Purchased source may be used in personal, commercial, and client projects. Do not redistribute or resell the original source. Terms of Service

Designyff © 2026 | Starter kits for developers | Powered by  Tacko