Skip to main content

Command Palette

Search for a command to run...

How to Build a Full-Stack SaaS App with Next.js?

Step-by-step guide to building a production-ready SaaS application using Next.js, and Stripe

Updated
โ€ข4 min read
How to Build a Full-Stack SaaS App with Next.js?
N
Hi there! I'm a Full-Stack Developer and AI Automation Engineer crafting innovative web applications and automation workflows that saves time, reduces effort, scale your growth, and drive real results.

Why Next.js for SaaS

Next.js is the go-to framework for building modern SaaS applications in 2025. It gives you server-side rendering, API routes, and seamless deployment โ€” all in one package. Combined with MongoDB and Stripe, you can ship a complete SaaS product faster than ever.

In this guide, I'll show you exactly how I build full-stack SaaS apps โ€” from project setup to live deployment.

What You'll Need

Before we start, make sure you have these ready:

  • โœ… Node.js โ€” v18 or higher

  • โœ… MongoDB Atlas account

  • โœ… Stripe account โ€” for payments

  • โœ… Vercel account โ€” for deployment

  • โœ… Basic knowledge of React and JavaScript

๐Ÿงฐ Tech Stack

Tool Role
Next.js 14 Full-stack framework
MongoDB Atlas Cloud database
Mongoose MongoDB ORM
NextAuth.js Authentication
Stripe Subscription payments
Tailwind CSS Styling
Vercel Deployment

Step 1 โ€” Project Setup

Open your terminal and run:

npx create-next-app@latest my-saas-app
cd my-saas-app
npm install mongoose next-auth stripe @stripe/stripe-js

Create your .env.local file:

MONGODB_URI=your_mongodb_connection_string
GOOGLE_ID=your_google_client_id
GOOGLE_SECRET=your_google_client_secret
NEXTAUTH_SECRET=your_random_secret
NEXTAUTH_URL=http://localhost:3000
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_PRICE_ID=your_stripe_price_id
NEXT_PUBLIC_URL=http://localhost:3000

Step 2 โ€” Connect MongoDB

Create /lib/mongodb.js:

import mongoose from 'mongoose'

const connectDB = async () => {
  if (mongoose.connections[0].readyState) return
  
  try {
    await mongoose.connect(process.env.MONGODB_URI)
    console.log('MongoDB connected successfully')
  } catch (error) {
    console.error('MongoDB connection error:', error)
  }
}

export default connectDB

Step 3 โ€” Setup Authentication

Create /app/api/auth/[...nextauth]/route.js:

import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
import connectDB from '@/lib/mongodb'
import User from '@/models/User'

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    })
  ],
  callbacks: {
    async signIn({ user }) {
      await connectDB()
      const existingUser = await User.findOne({ email: user.email })
      if (!existingUser) {
        await User.create({
          name: user.name,
          email: user.email,
          image: user.image,
        })
      }
      return true
    }
  }
}

const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

Step 4 โ€” Stripe Subscription

Create /app/api/create-checkout/route.js:

import Stripe from 'stripe'
import { getServerSession } from 'next-auth'
import { authOptions } from '../auth/[...nextauth]/route'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

export async function POST(req) {
  const session = await getServerSession(authOptions)
  
  if (!session) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const checkoutSession = await stripe.checkout.sessions.create({
    mode: 'subscription',
    customer_email: session.user.email,
    payment_method_types: ['card'],
    line_items: [{
      price: process.env.STRIPE_PRICE_ID,
      quantity: 1,
    }],
    success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
  })

  return Response.json({ url: checkoutSession.url })
}

Step 5 โ€” Protect Routes

Create middleware.js in root:

export { default } from 'next-auth/middleware'

export const config = {
  matcher: ['/dashboard/:path*', '/settings/:path*']
}

Step 6 โ€” Handle Stripe Webhooks

Create /app/api/webhooks/stripe/route.js:

import Stripe from 'stripe'
import connectDB from '@/lib/mongodb'
import User from '@/models/User'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

export async function POST(req) {
  const body = await req.text()
  const sig = req.headers.get('stripe-signature')

  let event
  try {
    event = stripe.webhooks.constructEvent(
      body, sig, process.env.STRIPE_WEBHOOK_SECRET
    )
  } catch (err) {
    return Response.json({ error: 'Webhook error' }, { status: 400 })
  }

  await connectDB()

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object
    await User.findOneAndUpdate(
      { email: session.customer_email },
      { isPro: true }
    )
  }

  return Response.json({ received: true })
}

Step 7 โ€” Deploy on Vercel

# Install Vercel CLI
npm install -g vercel

# Deploy
vercel

# Set environment variables in Vercel dashboard
# Then deploy to production
vercel --prod

Wrapping Up

Building a SaaS app with Next.js becomes easy when you break it into small steps:

  • โœ… Setup project
  • โœ… Connect database
  • โœ… Add authentication
  • โœ… Integrate payments
  • โœ… Protect routes
  • โœ… Handle webhooks
  • โœ… Deploy

The best SaaS is the one that ships. Start small, iterate fast, and keep building.

Found this helpful? Follow me for more full-stack development content. ๐Ÿš€