<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sanjana-03</title>
    <description>The latest articles on DEV Community by Sanjana-03 (@sanjana03).</description>
    <link>https://dev.to/sanjana03</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F510195%2Fce00be40-72ae-4b45-a453-ab3891ae4cc6.png</url>
      <title>DEV Community: Sanjana-03</title>
      <link>https://dev.to/sanjana03</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanjana03"/>
    <language>en</language>
    <item>
      <title>I Built a Production-Ready Next.js SaaS Starter Kit in 2 Weekends — Here's Everything I Included</title>
      <dc:creator>Sanjana-03</dc:creator>
      <pubDate>Wed, 15 Apr 2026 14:03:00 +0000</pubDate>
      <link>https://dev.to/sanjana03/i-built-a-production-ready-nextjs-saas-starter-kit-in-2-weekends-heres-everything-i-included-382d</link>
      <guid>https://dev.to/sanjana03/i-built-a-production-ready-nextjs-saas-starter-kit-in-2-weekends-heres-everything-i-included-382d</guid>
      <description>&lt;p&gt;Every time I started a new SaaS project, I found myself spending the first week doing the exact same thing — setting up authentication, connecting a database, wiring up payments, building a dashboard layout. The actual product hadn't even started yet and I'd already burned 7 days.&lt;/p&gt;

&lt;p&gt;So I decided to build it once, do it properly, and package it as a reusable starter kit. Two weekends later, it was done. Here's exactly what I built, the decisions I made, and the problems I ran into.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;I wanted a stack that was modern, well-documented, and something most developers would actually want to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next.js 14 (App Router)&lt;/strong&gt;&lt;br&gt;
The App Router is the future of Next.js. Server components, nested layouts, and file-based routing make it the obvious choice for any new SaaS project in 2024.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clerk (Authentication)&lt;/strong&gt;&lt;br&gt;
I chose Clerk over NextAuth for one simple reason — it just works. Pre-built sign in, sign up, user management UI, webhook support, and it looks great out of the box. NextAuth requires a lot more configuration to get the same result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prisma + Supabase (Database)&lt;/strong&gt;&lt;br&gt;
Prisma gives you type-safe database queries in TypeScript without writing raw SQL. Supabase gives you a free hosted PostgreSQL database with a great dashboard. Together they're the fastest way to get a production database running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS + Shadcn/ui (Styling)&lt;/strong&gt;&lt;br&gt;
Tailwind for utility-first styling, Shadcn for pre-built accessible components. No custom CSS, no fighting with component libraries. Just clean, consistent UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripe (Payments)&lt;/strong&gt;&lt;br&gt;
Stripe is the industry standard for SaaS billing. Subscription management, customer portal, webhooks — everything you need is already there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel (Deployment)&lt;/strong&gt;&lt;br&gt;
One-click deploy from GitHub. Automatic SSL, edge network, and preview deployments on every push. Built by the same team as Next.js so compatibility is perfect.&lt;/p&gt;


&lt;h2&gt;
  
  
  What's Included
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Authentication Flow
&lt;/h3&gt;

&lt;p&gt;Clerk handles the entire auth flow. Sign up, sign in, forgot password, user profile — all pre-built. Protected routes are handled via middleware so the dashboard is completely locked down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clerkMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createRouteMatcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProtectedRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRouteMatcher&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard(.*)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;clerkMiddleware&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isProtectedRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;protect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After sign in, users are automatically redirected to the dashboard. After sign out, they go back to the landing page. Zero configuration needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Schema
&lt;/h3&gt;

&lt;p&gt;Two models cover everything you need to get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
  id           String        @id @default(cuid())
  clerkId      String        @unique
  email        String        @unique
  name         String?
  createdAt    DateTime      @default(now())
  subscription Subscription?
}

model Subscription {
  id                     String    @id @default(cuid())
  userId                 String    @unique
  stripeCustomerId       String    @unique
  stripeSubscriptionId   String?
  stripePriceId          String?
  status                 String    @default("inactive")
  currentPeriodEnd       DateTime?
  user                   User      @relation(fields: [userId], references: [id])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user signs up via Clerk, a webhook fires and saves them to the database. When they upgrade via Stripe, the subscription record is created and linked to their user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard Layout
&lt;/h3&gt;

&lt;p&gt;The dashboard has a responsive sidebar with navigation, a top navbar with user avatar and dropdown, and mobile hamburger menu support. Built with Shadcn components so everything is accessible and keyboard-navigable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/(dashboard)/
  layout.tsx        ← Sidebar + navbar shell
  page.tsx          ← Overview with stat cards
  billing/page.tsx  ← Current plan + upgrade
  settings/page.tsx ← Profile + preferences
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stripe Integration
&lt;/h3&gt;

&lt;p&gt;Three API routes handle the entire billing flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/api/stripe/checkout&lt;/code&gt; — creates a Stripe checkout session&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/stripe/portal&lt;/code&gt; — opens the customer billing portal&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/api/webhooks/stripe&lt;/code&gt; — handles subscription events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The webhook handler listens for &lt;code&gt;checkout.session.completed&lt;/code&gt;, &lt;code&gt;customer.subscription.updated&lt;/code&gt;, and &lt;code&gt;customer.subscription.deleted&lt;/code&gt; — updating the database accordingly so your app always knows the user's current plan status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Landing Page
&lt;/h3&gt;

&lt;p&gt;A complete marketing page with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navbar with sign in / get started CTAs&lt;/li&gt;
&lt;li&gt;Hero with headline and subheadline&lt;/li&gt;
&lt;li&gt;Features section (6 cards)&lt;/li&gt;
&lt;li&gt;Pricing table (Free / Pro / Business)&lt;/li&gt;
&lt;li&gt;FAQ accordion&lt;/li&gt;
&lt;li&gt;Footer with links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fully responsive and dark mode ready via &lt;code&gt;next-themes&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Folder Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  (marketing)/      ← Landing page
  (auth)/           ← Sign in / sign up
  (dashboard)/      ← Protected dashboard
components/
  ui/               ← Shadcn components
  marketing/        ← Landing page sections
  dashboard/        ← Sidebar, stat cards, etc.
lib/
  stripe.ts         ← Stripe client + helpers
  db.ts             ← Prisma client
  subscription.ts   ← getUserSubscriptionPlan()
prisma/
  schema.prisma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Hardest Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prisma on Vercel
&lt;/h3&gt;

&lt;p&gt;This one caught me off guard. Everything worked perfectly locally but the Vercel deployment kept failing with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Module '"@prisma/client"' has no exported member 'PrismaClient'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix was simple but not obvious — Vercel doesn't run &lt;code&gt;prisma generate&lt;/code&gt; automatically. You have to add it to your build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prisma generate &amp;amp;&amp;amp; next build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prisma generate"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;postinstall&lt;/code&gt; script runs after &lt;code&gt;npm install&lt;/code&gt; on Vercel, generating the Prisma client before the build starts. One line fix, but it took a while to figure out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syncing Clerk Users to the Database
&lt;/h3&gt;

&lt;p&gt;Clerk handles authentication but your database knows nothing about new users until you tell it. The solution is a Clerk webhook that fires on &lt;code&gt;user.created&lt;/code&gt; and writes to your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/webhooks/clerk/route.ts&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;clerkId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email_addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;email_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, Stripe has no user to attach a subscription to when someone upgrades.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stripe Webhook Reliability
&lt;/h3&gt;

&lt;p&gt;Stripe webhooks need to be verified to prevent fake requests. Every webhook handler needs this check before processing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skipping this verification is a security hole. The starter kit handles it correctly out of the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Would Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the webhook setup earlier.&lt;/strong&gt; I built the entire dashboard before setting up the Clerk → database sync, which meant none of my test users were in the database. Set up webhooks on day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Lemon Squeezy instead of Stripe&lt;/strong&gt; if you're outside the US. Stripe has restrictions in some countries. Lemon Squeezy works globally, handles VAT automatically, and has a very similar API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add dark mode from the start.&lt;/strong&gt; I added it at the end and had to touch almost every component. Building with &lt;code&gt;dark:&lt;/code&gt; classes from day one saves a lot of time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm planning to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email notifications via Resend&lt;/li&gt;
&lt;li&gt;Team/organization support&lt;/li&gt;
&lt;li&gt;Usage-based billing support&lt;/li&gt;
&lt;li&gt;More dashboard page templates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you want to skip 2 weeks of boilerplate setup and start building your actual product on day one, I've packaged everything into a ready-to-use starter kit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://saas-template-fzx94d96j-raghuneha2803-5957s-projects.vercel.app/" rel="noopener noreferrer"&gt;https://saas-template-fzx94d96j-raghuneha2803-5957s-projects.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get the kit:&lt;/strong&gt; &lt;a href="https://raghuneha.gumroad.com/l/kvxtj" rel="noopener noreferrer"&gt;https://raghuneha.gumroad.com/l/kvxtj&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Includes full source code, README with step-by-step setup instructions, and deploys to Vercel in one click.&lt;/p&gt;

&lt;p&gt;Happy to answer any questions in the comments. What stack are you using for your SaaS projects?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
