How to Build a SaaS in 30 Days with Next.js and Tailwind

Photo by Unsplash

Photo by Unsplash
I built my first SaaS in 47 days and it was a mess. The second one took 28 days and shipped with working Stripe subscriptions, email automation, and a dashboard that didn't embarrass me. The difference wasn't skill — it was having a week-by-week framework before writing a single line of code. This is that framework, adapted from real projects using Next.js 15, Tailwind CSS, Prisma, and Stripe.
Solo developers fail at SaaS not because they can't code but because they spend too long on the wrong things. Week 1 is foundation: project scaffold, database schema, and authentication. Week 2 is the core feature — your actual value proposition. Week 3 is business infrastructure: payments, email, and access control. Week 4 is ship: SEO, performance, staging, and launch. The 30-day timeline forces prioritization. Everything that doesn't make this list is a v2 feature.
Don't start from zero — use a template or starter that already has Tailwind, TypeScript strict mode, and ESLint configured. I use my own Next.js starter with shadcn/ui, next-intl for i18n, and a Prisma singleton already wired up. On day 3-4, design your Prisma schema before touching UI — the schema is your application's skeleton. Get User, Session (via NextAuth), Subscription, and your core domain model right on day 1. Migrations are cheap now, expensive with real data.
This is where most solo devs go wrong. They build settings pages, notification preferences, and profile editors before building the thing users actually pay for. In week 2, build one feature end-to-end: create, list, edit, delete, with proper validation and error handling. Make it ugly but functional. Polish comes in week 3. For a project management SaaS, this means projects + tasks working. For an invoice tool, this means create + PDF preview working. Ship the value loop before the periphery.
Week 1 — Foundation
Day 1-2 : Next.js 15 scaffold + Tailwind + shadcn/ui
Day 3-4 : Prisma schema + PostgreSQL + auth (NextAuth v5)
Day 5-7 : Core data models + basic CRUD API routes
Week 2 — Core Features
Day 8-10 : Dashboard layout + sidebar navigation
Day 11-12 : Feature #1 (your SaaS's main value prop)
Day 13-14 : Stripe integration (subscription + webhooks)
Week 3 — Polish & UX
Day 15-17 : Onboarding flow + empty states
Day 18-19 : Email (Resend) — welcome, receipts, alerts
Day 20-21 : Role-based access control (free / pro / admin)
Week 4 — Ship
Day 22-24 : SEO metadata + OG images + sitemap
Day 25-26 : Error boundaries + loading states + toasts
Day 27-28 : Performance audit (Lighthouse ≥ 90)
Day 29 : Staging deploy on Vercel + smoke tests
Day 30 : Production launch → ProductHunt / XUse shadcn/ui components from day one — they're unstyled-enough to look like your brand but complete enough to save days of work. Add them with 'npx shadcn@latest add button input table dialog' and you have a professional UI foundation in 10 minutes. Customize the CSS variables in globals.css rather than fighting the component internals.
Stripe integration has two parts: the checkout flow and the webhook handler. The checkout is simple — create a checkout session, redirect the user. The webhook is where real work happens. You need to handle subscription.created, subscription.updated, subscription.deleted, and invoice.payment_failed events. Each one needs to update your database and potentially trigger an email. Use an idempotency key on webhook processing to avoid double-updates on Stripe retries.
Resend is the best transactional email service for Next.js developers — it has a native React SDK so your email templates are just React components. Wire up three emails in week 3: welcome email on signup, receipt on successful payment, and a failed payment warning. Don't over-engineer email; a simple plain-text email with the Resend SDK beats a broken fancy template every time.
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from "next/server"
import Stripe from "stripe"
import { prisma } from "@/lib/db"
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(req: NextRequest) {
const body = await req.text()
const signature = req.headers.get("stripe-signature")!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
} catch {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 })
}
switch (event.type) {
case "customer.subscription.created":
case "customer.subscription.updated": {
const sub = event.data.object as Stripe.Subscription
await prisma.subscription.upsert({
where: { stripeSubscriptionId: sub.id },
update: {
status: sub.status,
currentPeriodEnd: new Date(sub.current_period_end * 1000),
plan: sub.items.data[0].price.lookup_key ?? "pro",
},
create: {
stripeSubscriptionId: sub.id,
stripeCustomerId: sub.customer as string,
status: sub.status,
currentPeriodEnd: new Date(sub.current_period_end * 1000),
plan: sub.items.data[0].price.lookup_key ?? "pro",
},
})
break
}
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription
await prisma.subscription.update({
where: { stripeSubscriptionId: sub.id },
data: { status: "canceled" },
})
break
}
}
return NextResponse.json({ received: true })
}The last week is about making what you built discoverable and stable. Add Next.js Metadata API to every public page. Generate OG images using next/og. Run Lighthouse on every key page and fix anything below 85. Set up error boundaries so one broken component doesn't crash the whole dashboard. Add toast notifications for every async action. Test on a real mobile device, not just responsive view in DevTools.
The week 4 temptation is to add 'just one more feature' before launch. Resist it. Every new feature added in the final week is untested, unpolished, and delays launch. Write it in your backlog, ship what you promised in week 1-3, and launch. You can ship v1.1 next week. A shipped SaaS with five features beats a perpetually-in-development SaaS with fifty features every single time.
Three things kill the timeline: (1) Perfect UI syndrome — spending a full day on button hover states in week 1. Use shadcn defaults, move on. (2) Over-engineered auth — don't build your own session management. NextAuth v5 or Clerk handles it in an afternoon. (3) No deploy until week 4 — deploy to Vercel on day 1 with a placeholder page. Having a real URL forces real decisions about environment, database connection strings, and deployment pipeline before you're under deadline pressure.
Next.js 15 App Router for the frontend and API routes. Prisma + PostgreSQL (Neon or Supabase free tier for solo projects). NextAuth v5 for authentication with Google and GitHub providers. Tailwind CSS + shadcn/ui for UI. Stripe for payments. Resend for email. Vercel for hosting. This stack lets a solo developer build what used to take a team because each library has excellent TypeScript types, great docs, and minimal configuration.