T

TechIdea

Ecosystem

Back to nextjs Projects
Advanced Level

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

API Design

IntegrationAPI

Internal Next.js Server Actions.

Step-by-Step Implementation

1

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

nextjs
// 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

2

Set up database schema (Users, Customers, Deals).

### Step 2: Database Schema Define the Customer and Deal models. Run database migrations.

nextjs
// 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

3

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')`.

nextjs
// 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

4

Implement Server Actions for creating and editing customers.

### Step 4: Error Handling Use `error.tsx` boundaries to catch database connection failures gracefully.

nextjs
// 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

5

Fetch data directly in Server Components to eliminate loading spinners.

### Step 4: Error Handling Use `error.tsx` boundaries to catch database connection failures gracefully.

nextjs
// 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

Server action throws "Cannot read property of undefined".

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.

Growth Newsletter

Get practical AI tools, SEO tips, and growth guides weekly.

Join creators, students, and businesses scaling with TechIdea.