SaaS

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

We earn commissions when you shop through the links below.

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

4 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.

Production uses the same contract: a host whose app process can read DATABASE_URL, plus a migration step that runs before the new version takes traffic. If you want the app and Postgres in one project, deploying Next.js with Prisma and Postgres on Railway covers the settings that matter there: a standalone build, DATABASE_URL as a reference variable, a pre-deploy prisma migrate deploy, and keeping the service that receives Stripe webhooks from sleeping.

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 when you only need the billing foundation. It 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.

Launching an MVP quickly

Ship this order and stop when a stranger can pay:

  1. Auth that creates a user row and a session cookie.
  2. A dashboard that redirects when currentUser() is empty.
  3. Postgres running, even if the only table is User.
  4. One Checkout session and a webhook that writes plan.
  5. One paid route that reads plan === "pro" and nothing else.

Do not add teams, invoices, or a marketing site redesign before those five work. The authentication architecture and Stripe Customer Portal articles cover the two pieces people usually postpone.

What a small SaaS architecture should look like

Keep three folders: src/lib for session, database, and Stripe clients; src/app/api/stripe for money; src/app for pages. Pages never construct a Stripe client. Webhooks never render HTML. That split is also how the Next.js SaaS boilerplate stays readable, and why the Complete kit can add Docker and email without changing the page tree.

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

Not ready to buy? Start with the Free Next.js SaaS Landing Page. €0.

Product updates for developers shipping with Designyff kits.

Guides: Next.js, Stripe, and SaaS starter kits Professional website design for small businesses

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

Designyff © 2026 | Starter kits for developers