Building a MERN App with Next.js: Complete Guide
Learn how to build a full-stack MERN application using Next.js as the frontend framework — from setup to deployment, including performance and SEO practices.
Introduction
The MERN stack (MongoDB, Express.js, React, Node.js) is one of the most popular full-stack combinations. Replacing a plain React SPA with Next.js adds SSR/SSG, routing, and built-in optimization.
This guide walks through a task management app that covers authentication, CRUD, and a practical deploy path.
Setup & architecture
Two parts: a Next.js frontend and a Node/Express API. The frontend owns UI and routing; the backend owns auth, data, and API endpoints.
Frontend
npx create-next-app@latest task-manager-frontend
cd task-manager-frontend
npm install axios @tanstack/react-queryBackend
mkdir task-manager-backend
cd task-manager-backend
npm init -y
npm install express mongoose cors dotenv bcryptjs jsonwebtoken
npm install -D nodemonMongoDB models
Two collections — Users and Tasks — keep ownership clear while staying simple to query.
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
tasks: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Task' }]
}, { timestamps: true });const taskSchema = new mongoose.Schema({
title: { type: String, required: true },
description: String,
completed: { type: Boolean, default: false },
priority: {
type: String,
enum: ['low', 'medium', 'high'],
default: 'medium'
},
dueDate: Date,
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
}, { timestamps: true });Express API
REST endpoints for auth and tasks, with JWT middleware and clear error responses.
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Invalid token' });
req.user = user;
next();
});
};Next.js frontend
App Router for structure, React Query for server state, and a mix of server/client components where each fits.
const useTasks = () => {
return useQuery({
queryKey: ['tasks'],
queryFn: async () => {
const response = await fetch('/api/tasks', {
headers: { Authorization: `Bearer ${getToken()}` }
});
return response.json();
}
});
};Performance & SEO
- Prefer Next.js Image for media
- Ship solid meta tags and Open Graph
- Dynamic-import heavy client modules
- Cache API responses thoughtfully
- Keep bundles lean with tree-shaking
Deploy
Common split: Vercel for Next.js, a Node host for the API, and MongoDB Atlas for data.
# Frontend (.env.local)
NEXT_PUBLIC_API_URL=https://your-api-domain.com
# Backend (.env)
MONGODB_URI=mongodb+srv://...
JWT_SECRET=your-super-secret-jwt-key
PORT=5000Wrap-up
MERN + Next.js is a strong default for apps that need a real API and a fast, SEO-friendly frontend. Extend with sockets, uploads, or OAuth when the product needs them.