T

TechIdea

Ecosystem

Back to nextjs Projects
Advanced Level

Next.js 14 Admin Dashboard with Tailwind CSS

A high-performance Admin Dashboard boilerplate built with Next.js 14 App Router, featuring a responsive sidebar, data tables, and Recharts analytics integration.

The Problem

Used by internal teams to monitor metrics, manage users, and process data. Dashboards are the backbone of B2B SaaS applications.

Real-World Use Case

Used by internal teams to monitor metrics, manage users, and process data. Dashboards are the backbone of B2B SaaS applications.

Technology Stack

Next.js App Router mechanics

Prerequisite

Tailwind CSS utility classes

Prerequisite

React Server Components vs Client Components

Prerequisite

Architecture & Design

Folder Structure

src/
├── app/
│   ├── (dashboard)/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── globals.css
├── components/
│   ├── Sidebar.tsx
│   ├── TopNav.tsx
│   └── MetricCard.tsx

API Design

IntegrationAPI

Utilizes Next.js Server Actions for secure, API-less data fetching directly from the database.

Step-by-Step Implementation

1

Set up Next.js 14 App Router with Tailwind.

### Step 1: Project Initialization Run `npx create-next-app@latest my-dashboard`. Select TypeScript, Tailwind CSS, and App Router. Install Lucide Icons for UI elements (`npm i lucide-react`).

nextjs
// Full source code is split across components in a real Next.js app.
// Here is a consolidated visualization of the Dashboard Page (page.tsx)

import { MetricCard } from '@/components/MetricCard';
import { Users, DollarSign, Activity, ShoppingCart } from 'lucide-react';

export default async function DashboardPage() {
  // Simulate server-side data fetching
  // const metrics = await db.query('SELECT ...')
  
  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard Overview</h1>
        <button className="px-4 py-2 bg-blue-600 text-white text-sm font-bold rounded-lg shadow-sm">
          Download Report
        </button>
      </div>

      <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Total Revenue</h3>
            <DollarSign className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">$45,231.89</div>
          <p className="text-xs text-emerald-600 mt-1">+20.1% from last month</p>
        </div>
        
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Users</h3>
            <Users className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+2350</div>
          <p className="text-xs text-emerald-600 mt-1">+180 new today</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Sales</h3>
            <ShoppingCart className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+12,234</div>
          <p className="text-xs text-rose-600 mt-1">-4% from last month</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Now</h3>
            <Activity className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+573</div>
          <p className="text-xs text-emerald-600 mt-1">+201 since last hour</p>
        </div>
      </div>

      {/* Chart Section Placeholder */}
      <div className="grid gap-6 grid-cols-1 lg:grid-cols-7">
        <div className="col-span-1 lg:col-span-4 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recharts Line Chart Goes Here</p>
        </div>
        <div className="col-span-1 lg:col-span-3 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recent Activity List Goes Here</p>
        </div>
      </div>
    </div>
  );
}

Code Explanation

Implementation step

2

Create a (dashboard) route group to share a common layout.

### Step 2: The Dashboard Layout Create `app/(dashboard)/layout.tsx`. This file ensures the Sidebar and TopNav persist across all dashboard pages without re-rendering. ```tsx import Sidebar from '@/components/Sidebar'; import TopNav from '@/components/TopNav'; export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen bg-slate-50"> <Sidebar /> <div className="flex-1 flex flex-col overflow-hidden"> <TopNav /> <main className="flex-1 overflow-y-auto p-6"> {children} </main> </div> </div> ); } ```

nextjs
// Full source code is split across components in a real Next.js app.
// Here is a consolidated visualization of the Dashboard Page (page.tsx)

import { MetricCard } from '@/components/MetricCard';
import { Users, DollarSign, Activity, ShoppingCart } from 'lucide-react';

export default async function DashboardPage() {
  // Simulate server-side data fetching
  // const metrics = await db.query('SELECT ...')
  
  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard Overview</h1>
        <button className="px-4 py-2 bg-blue-600 text-white text-sm font-bold rounded-lg shadow-sm">
          Download Report
        </button>
      </div>

      <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Total Revenue</h3>
            <DollarSign className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">$45,231.89</div>
          <p className="text-xs text-emerald-600 mt-1">+20.1% from last month</p>
        </div>
        
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Users</h3>
            <Users className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+2350</div>
          <p className="text-xs text-emerald-600 mt-1">+180 new today</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Sales</h3>
            <ShoppingCart className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+12,234</div>
          <p className="text-xs text-rose-600 mt-1">-4% from last month</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Now</h3>
            <Activity className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+573</div>
          <p className="text-xs text-emerald-600 mt-1">+201 since last hour</p>
        </div>
      </div>

      {/* Chart Section Placeholder */}
      <div className="grid gap-6 grid-cols-1 lg:grid-cols-7">
        <div className="col-span-1 lg:col-span-4 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recharts Line Chart Goes Here</p>
        </div>
        <div className="col-span-1 lg:col-span-3 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recent Activity List Goes Here</p>
        </div>
      </div>
    </div>
  );
}

Code Explanation

Implementation step

3

Build the Sidebar component with navigational links.

### Step 3: Creating Metric Cards Create reusable UI components to display key performance indicators. ```tsx export function MetricCard({ title, value, trend }: { title: string, value: string, trend: string }) { return ( <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm"> <h3 className="text-sm font-semibold text-slate-500">{title}</h3> <div className="mt-2 flex items-baseline gap-2"> <span className="text-3xl font-bold text-slate-900">{value}</span> <span className="text-sm font-medium text-emerald-600">{trend}</span> </div> </div> ); } ```

nextjs
// Full source code is split across components in a real Next.js app.
// Here is a consolidated visualization of the Dashboard Page (page.tsx)

import { MetricCard } from '@/components/MetricCard';
import { Users, DollarSign, Activity, ShoppingCart } from 'lucide-react';

export default async function DashboardPage() {
  // Simulate server-side data fetching
  // const metrics = await db.query('SELECT ...')
  
  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard Overview</h1>
        <button className="px-4 py-2 bg-blue-600 text-white text-sm font-bold rounded-lg shadow-sm">
          Download Report
        </button>
      </div>

      <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Total Revenue</h3>
            <DollarSign className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">$45,231.89</div>
          <p className="text-xs text-emerald-600 mt-1">+20.1% from last month</p>
        </div>
        
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Users</h3>
            <Users className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+2350</div>
          <p className="text-xs text-emerald-600 mt-1">+180 new today</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Sales</h3>
            <ShoppingCart className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+12,234</div>
          <p className="text-xs text-rose-600 mt-1">-4% from last month</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Now</h3>
            <Activity className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+573</div>
          <p className="text-xs text-emerald-600 mt-1">+201 since last hour</p>
        </div>
      </div>

      {/* Chart Section Placeholder */}
      <div className="grid gap-6 grid-cols-1 lg:grid-cols-7">
        <div className="col-span-1 lg:col-span-4 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recharts Line Chart Goes Here</p>
        </div>
        <div className="col-span-1 lg:col-span-3 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recent Activity List Goes Here</p>
        </div>
      </div>
    </div>
  );
}

Code Explanation

Implementation step

4

Build the TopNav component with a user avatar and search bar.

### Step 4: Loading States Add a `loading.tsx` file inside the (dashboard) directory. Next.js will automatically display this skeleton UI while data is being fetched on the server.

nextjs
// Full source code is split across components in a real Next.js app.
// Here is a consolidated visualization of the Dashboard Page (page.tsx)

import { MetricCard } from '@/components/MetricCard';
import { Users, DollarSign, Activity, ShoppingCart } from 'lucide-react';

export default async function DashboardPage() {
  // Simulate server-side data fetching
  // const metrics = await db.query('SELECT ...')
  
  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard Overview</h1>
        <button className="px-4 py-2 bg-blue-600 text-white text-sm font-bold rounded-lg shadow-sm">
          Download Report
        </button>
      </div>

      <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Total Revenue</h3>
            <DollarSign className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">$45,231.89</div>
          <p className="text-xs text-emerald-600 mt-1">+20.1% from last month</p>
        </div>
        
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Users</h3>
            <Users className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+2350</div>
          <p className="text-xs text-emerald-600 mt-1">+180 new today</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Sales</h3>
            <ShoppingCart className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+12,234</div>
          <p className="text-xs text-rose-600 mt-1">-4% from last month</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Now</h3>
            <Activity className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+573</div>
          <p className="text-xs text-emerald-600 mt-1">+201 since last hour</p>
        </div>
      </div>

      {/* Chart Section Placeholder */}
      <div className="grid gap-6 grid-cols-1 lg:grid-cols-7">
        <div className="col-span-1 lg:col-span-4 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recharts Line Chart Goes Here</p>
        </div>
        <div className="col-span-1 lg:col-span-3 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recent Activity List Goes Here</p>
        </div>
      </div>
    </div>
  );
}

Code Explanation

Implementation step

5

Construct the main dashboard page using CSS Grid to organize MetricCards.

### Step 4: Loading States Add a `loading.tsx` file inside the (dashboard) directory. Next.js will automatically display this skeleton UI while data is being fetched on the server.

nextjs
// Full source code is split across components in a real Next.js app.
// Here is a consolidated visualization of the Dashboard Page (page.tsx)

import { MetricCard } from '@/components/MetricCard';
import { Users, DollarSign, Activity, ShoppingCart } from 'lucide-react';

export default async function DashboardPage() {
  // Simulate server-side data fetching
  // const metrics = await db.query('SELECT ...')
  
  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold tracking-tight text-slate-900">Dashboard Overview</h1>
        <button className="px-4 py-2 bg-blue-600 text-white text-sm font-bold rounded-lg shadow-sm">
          Download Report
        </button>
      </div>

      <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Total Revenue</h3>
            <DollarSign className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">$45,231.89</div>
          <p className="text-xs text-emerald-600 mt-1">+20.1% from last month</p>
        </div>
        
        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Users</h3>
            <Users className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+2350</div>
          <p className="text-xs text-emerald-600 mt-1">+180 new today</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Sales</h3>
            <ShoppingCart className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+12,234</div>
          <p className="text-xs text-rose-600 mt-1">-4% from last month</p>
        </div>

        <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
          <div className="flex items-center justify-between pb-2">
            <h3 className="text-sm font-semibold text-slate-500">Active Now</h3>
            <Activity className="h-4 w-4 text-slate-400" />
          </div>
          <div className="text-3xl font-bold">+573</div>
          <p className="text-xs text-emerald-600 mt-1">+201 since last hour</p>
        </div>
      </div>

      {/* Chart Section Placeholder */}
      <div className="grid gap-6 grid-cols-1 lg:grid-cols-7">
        <div className="col-span-1 lg:col-span-4 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recharts Line Chart Goes Here</p>
        </div>
        <div className="col-span-1 lg:col-span-3 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm h-96 flex items-center justify-center">
          <p className="text-slate-400 font-medium">Recent Activity List Goes Here</p>
        </div>
      </div>
    </div>
  );
}

Code Explanation

Implementation step

Common Errors

Window object is undefined.

If using Recharts, ensure the chart component has 'use client' at the top of the file.

Layout rerenders on every page change.

Ensure the layout is in layout.tsx, not imported into individual pages.

Security & Performance

Resize browser window to verify the grid collapses to single columns on mobile devices.

Verify the Sidebar toggles open/closed correctly on mobile viewports.

Simulate a slow network connection to ensure loading.tsx skeleton appears.


Add Dark Mode toggling using next-themes.

Integrate next-intl for multi-language support on the dashboard.

Export charts to PDF/Image.

Interview Questions

Q: Should my Dashboard components be Server or Client components?

A: Keep layouts and data-fetching pages as Server Components. Only use Client Components ('use client') for interactive UI elements like Charts, Modals, and the Sidebar toggle.

Growth Newsletter

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

Join creators, students, and businesses scaling with TechIdea.