Next.js CRM Dashboard Project
Build a Full-Stack CRM Dashboard using Next.js 14 App Router, Server Actions, Tailwind CSS, and a PostgreSQL database. Perfect for your portfolio.
The Problem
A CRM is the backbone of any sales agency. Building this proves you can handle relational data, authentication, and state management—the three pillars of enterprise SaaS.
Real-World Use Case
A CRM is the backbone of any sales agency. Building this proves you can handle relational data, authentication, and state management—the three pillars of enterprise SaaS.
Technology Stack
Next.js App Router
Prerequisite
React Server Components
Prerequisite
Tailwind CSS
Prerequisite
Basic SQL
Prerequisite
Architecture & Design
Folder Structure
crm-dashboard/
├── app/
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── customers/page.tsx
│ ├── api/
│ └── layout.tsx
└── lib/
├── db.ts
└── actions.tsAPI Design
APIInternal Next.js Server Actions.
Step-by-Step Implementation
Initialize Next.js project with Tailwind CSS.
### Step 1: Project Setup Run `npx create-next-app@latest` and install your ORM of choice (like Prisma).
// Example Server Action
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createCustomer(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.customer.create({
data: { name, email, status: 'Active' }
});
revalidatePath('/customers');
}Code Explanation
Implementation step
Set up database schema (Users, Customers, Deals).
### Step 2: Database Schema Define the Customer and Deal models. Run database migrations.
// Example Server Action
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createCustomer(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.customer.create({
data: { name, email, status: 'Active' }
});
revalidatePath('/customers');
}Code Explanation
Implementation step
Create a reusable sidebar layout for the dashboard.
### Step 3: Server Actions Write a `createCustomer` action in `lib/actions.ts` that inserts into the DB and calls `revalidatePath('/customers')`.
// Example Server Action
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createCustomer(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.customer.create({
data: { name, email, status: 'Active' }
});
revalidatePath('/customers');
}Code Explanation
Implementation step
Implement Server Actions for creating and editing customers.
### Step 4: Error Handling Use `error.tsx` boundaries to catch database connection failures gracefully.
// Example Server Action
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createCustomer(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.customer.create({
data: { name, email, status: 'Active' }
});
revalidatePath('/customers');
}Code Explanation
Implementation step
Fetch data directly in Server Components to eliminate loading spinners.
### Step 4: Error Handling Use `error.tsx` boundaries to catch database connection failures gracefully.
// Example Server Action
'use server'
import { revalidatePath } from 'next/cache';
import db from '@/lib/db';
export async function createCustomer(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
await db.customer.create({
data: { name, email, status: 'Active' }
});
revalidatePath('/customers');
}Code Explanation
Implementation step
Common Errors
Ensure you add 'use server' at the top of the actions file.
Security & Performance
Verify that creating a customer updates the list immediately without a hard refresh.
Test mobile responsiveness of the sidebar.
Add OAuth authentication using Auth.js.
Implement pagination for the customers table.
Interview Questions
Q: Why use Server Actions instead of API routes?
A: Server Actions provide end-to-end type safety and automatically handle form submissions without needing to write manually fetch() code.