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

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





