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. ๐
About the author
- 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.
Comments
Join the discussionNo comments yet. Be the first to comment.





