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.
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
- A server action or route handler reads the session and loads
User. - Public pages (home, pricing, login) do not require a session.
/dashboardredirects to login whencurrentUser()is empty.- Paid screens check
user.plan === "pro". They do not call Stripe. - Checkout and the webhook are the only places that write
stripeCustomerId,stripeSubscriptionId, andplan.
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:
- Auth that creates a user row and a session cookie.
- A dashboard that redirects when
currentUser()is empty. - Postgres running, even if the only table is
User. - One Checkout session and a webhook that writes
plan. - 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.