Next.js Admin Dashboard
Build an enterprise Admin Dashboard in Next.js demonstrating nested layouts, data fetching from mock APIs, chart rendering, metrics cards, and user table filtering.
The Problem
Build an enterprise Admin Dashboard in Next.js demonstrating nested layouts, data fetching from mock APIs, chart rendering, metrics cards, and user table filtering.
Real-World Use Case
Build an enterprise Admin Dashboard in Next.js demonstrating nested layouts, data fetching from mock APIs, chart rendering, metrics cards, and user table filtering.
Technology Stack
Understanding of Next.js nested layouts
Prerequisite
Familiarity with data fetching patterns
Prerequisite
Architecture & Design
Folder Structure
admin_dashboard/
├── app/
│ ├── dashboard/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── layout.tsx
└── package.jsonStep-by-Step Implementation
Create shared dashboard layout with navigation sidebar.
### Step 1: Project Setup Initialize Next.js project and setup dashboard folder routing.
import { useState } from 'react';
const INITIAL_USERS = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', status: 'Active' },
{ id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Editor', status: 'Active' },
{ id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer', status: 'Inactive' },
{ id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Editor', status: 'Active' },
];
export default function AdminDashboard() {
const [search, setSearch] = useState('');
const [users] = useState(INITIAL_USERS);
const filtered = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="flex min-h-screen bg-slate-50">
<aside className="w-64 bg-slate-900 text-white p-6 flex flex-col justify-between">
<div>
<span className="font-extrabold text-lg tracking-wider text-blue-400 block mb-10">TECHADMIN</span>
<nav className="space-y-3 text-xs font-semibold">
<a href="#" className="block p-3 bg-blue-600 rounded-xl text-white">📊 Analytics Overview</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">👥 User Management</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">⚙️ System Settings</a>
</nav>
</div>
<div className="pt-4 border-t border-slate-800 text-[10px] text-slate-500">
Admin OS v2.1
</div>
</aside>
<main className="flex-1 p-10 max-w-6xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-bold text-slate-900">Analytics Overview</h1>
<p className="text-xs text-slate-500">Welcome back, System Administrator</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Total Revenue</span>
<span className="text-3xl font-extrabold text-slate-900">$48,295</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +14% from last month</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Active Users</span>
<span className="text-3xl font-extrabold text-slate-900">2,491</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +5% from last week</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Server Load</span>
<span className="text-3xl font-extrabold text-slate-900">42%</span>
<span className="text-[10px] text-slate-500 block mt-2">Normal operating parameters</span>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h2 className="font-bold text-lg text-slate-900">User Management Directory</h2>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user by name or email..."
className="p-2.5 border border-slate-200 rounded-xl text-xs w-72"
/>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr class="bg-slate-50 border-b border-slate-200 text-slate-600 font-bold">
<th className="p-3">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Role</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-slate-700">
{filtered.map(user => (
<tr key={user.id} className="hover:bg-slate-50">
<td className="p-3 font-semibold text-slate-900">{user.name}</td>
<td className="p-3 text-slate-500">{user.email}</td>
<td className="p-3 font-medium">{user.role}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold ${user.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
);
}Code Explanation
Implementation step
Fetch metrics summary data on server component mount.
### Step 2: Layout & Data Fetching Implement dashboard layout sidebar and server data fetching.
import { useState } from 'react';
const INITIAL_USERS = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', status: 'Active' },
{ id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Editor', status: 'Active' },
{ id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer', status: 'Inactive' },
{ id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Editor', status: 'Active' },
];
export default function AdminDashboard() {
const [search, setSearch] = useState('');
const [users] = useState(INITIAL_USERS);
const filtered = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="flex min-h-screen bg-slate-50">
<aside className="w-64 bg-slate-900 text-white p-6 flex flex-col justify-between">
<div>
<span className="font-extrabold text-lg tracking-wider text-blue-400 block mb-10">TECHADMIN</span>
<nav className="space-y-3 text-xs font-semibold">
<a href="#" className="block p-3 bg-blue-600 rounded-xl text-white">📊 Analytics Overview</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">👥 User Management</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">⚙️ System Settings</a>
</nav>
</div>
<div className="pt-4 border-t border-slate-800 text-[10px] text-slate-500">
Admin OS v2.1
</div>
</aside>
<main className="flex-1 p-10 max-w-6xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-bold text-slate-900">Analytics Overview</h1>
<p className="text-xs text-slate-500">Welcome back, System Administrator</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Total Revenue</span>
<span className="text-3xl font-extrabold text-slate-900">$48,295</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +14% from last month</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Active Users</span>
<span className="text-3xl font-extrabold text-slate-900">2,491</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +5% from last week</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Server Load</span>
<span className="text-3xl font-extrabold text-slate-900">42%</span>
<span className="text-[10px] text-slate-500 block mt-2">Normal operating parameters</span>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h2 className="font-bold text-lg text-slate-900">User Management Directory</h2>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user by name or email..."
className="p-2.5 border border-slate-200 rounded-xl text-xs w-72"
/>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr class="bg-slate-50 border-b border-slate-200 text-slate-600 font-bold">
<th className="p-3">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Role</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-slate-700">
{filtered.map(user => (
<tr key={user.id} className="hover:bg-slate-50">
<td className="p-3 font-semibold text-slate-900">{user.name}</td>
<td className="p-3 text-slate-500">{user.email}</td>
<td className="p-3 font-medium">{user.role}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold ${user.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
);
}Code Explanation
Implementation step
Render summary metric cards (Total Revenue, Active Users).
### Step 3: Analytics Table & Filtering Build user data table with search query filtering.
import { useState } from 'react';
const INITIAL_USERS = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', status: 'Active' },
{ id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Editor', status: 'Active' },
{ id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer', status: 'Inactive' },
{ id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Editor', status: 'Active' },
];
export default function AdminDashboard() {
const [search, setSearch] = useState('');
const [users] = useState(INITIAL_USERS);
const filtered = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="flex min-h-screen bg-slate-50">
<aside className="w-64 bg-slate-900 text-white p-6 flex flex-col justify-between">
<div>
<span className="font-extrabold text-lg tracking-wider text-blue-400 block mb-10">TECHADMIN</span>
<nav className="space-y-3 text-xs font-semibold">
<a href="#" className="block p-3 bg-blue-600 rounded-xl text-white">📊 Analytics Overview</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">👥 User Management</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">⚙️ System Settings</a>
</nav>
</div>
<div className="pt-4 border-t border-slate-800 text-[10px] text-slate-500">
Admin OS v2.1
</div>
</aside>
<main className="flex-1 p-10 max-w-6xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-bold text-slate-900">Analytics Overview</h1>
<p className="text-xs text-slate-500">Welcome back, System Administrator</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Total Revenue</span>
<span className="text-3xl font-extrabold text-slate-900">$48,295</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +14% from last month</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Active Users</span>
<span className="text-3xl font-extrabold text-slate-900">2,491</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +5% from last week</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Server Load</span>
<span className="text-3xl font-extrabold text-slate-900">42%</span>
<span className="text-[10px] text-slate-500 block mt-2">Normal operating parameters</span>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h2 className="font-bold text-lg text-slate-900">User Management Directory</h2>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user by name or email..."
className="p-2.5 border border-slate-200 rounded-xl text-xs w-72"
/>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr class="bg-slate-50 border-b border-slate-200 text-slate-600 font-bold">
<th className="p-3">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Role</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-slate-700">
{filtered.map(user => (
<tr key={user.id} className="hover:bg-slate-50">
<td className="p-3 font-semibold text-slate-900">{user.name}</td>
<td className="p-3 text-slate-500">{user.email}</td>
<td className="p-3 font-medium">{user.role}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold ${user.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
);
}Code Explanation
Implementation step
Implement client component data table with search filtering.
### Step 4: Loading & Error States Add loading.tsx and error.tsx files for smooth transitions.
import { useState } from 'react';
const INITIAL_USERS = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', status: 'Active' },
{ id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Editor', status: 'Active' },
{ id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer', status: 'Inactive' },
{ id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Editor', status: 'Active' },
];
export default function AdminDashboard() {
const [search, setSearch] = useState('');
const [users] = useState(INITIAL_USERS);
const filtered = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="flex min-h-screen bg-slate-50">
<aside className="w-64 bg-slate-900 text-white p-6 flex flex-col justify-between">
<div>
<span className="font-extrabold text-lg tracking-wider text-blue-400 block mb-10">TECHADMIN</span>
<nav className="space-y-3 text-xs font-semibold">
<a href="#" className="block p-3 bg-blue-600 rounded-xl text-white">📊 Analytics Overview</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">👥 User Management</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">⚙️ System Settings</a>
</nav>
</div>
<div className="pt-4 border-t border-slate-800 text-[10px] text-slate-500">
Admin OS v2.1
</div>
</aside>
<main className="flex-1 p-10 max-w-6xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-bold text-slate-900">Analytics Overview</h1>
<p className="text-xs text-slate-500">Welcome back, System Administrator</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Total Revenue</span>
<span className="text-3xl font-extrabold text-slate-900">$48,295</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +14% from last month</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Active Users</span>
<span className="text-3xl font-extrabold text-slate-900">2,491</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +5% from last week</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Server Load</span>
<span className="text-3xl font-extrabold text-slate-900">42%</span>
<span className="text-[10px] text-slate-500 block mt-2">Normal operating parameters</span>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h2 className="font-bold text-lg text-slate-900">User Management Directory</h2>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user by name or email..."
className="p-2.5 border border-slate-200 rounded-xl text-xs w-72"
/>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr class="bg-slate-50 border-b border-slate-200 text-slate-600 font-bold">
<th className="p-3">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Role</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-slate-700">
{filtered.map(user => (
<tr key={user.id} className="hover:bg-slate-50">
<td className="p-3 font-semibold text-slate-900">{user.name}</td>
<td className="p-3 text-slate-500">{user.email}</td>
<td className="p-3 font-medium">{user.role}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold ${user.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
);
}Code Explanation
Implementation step
Render visual progress bars or chart representations.
### Step 4: Loading & Error States Add loading.tsx and error.tsx files for smooth transitions.
import { useState } from 'react';
const INITIAL_USERS = [
{ id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', status: 'Active' },
{ id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Editor', status: 'Active' },
{ id: '3', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Viewer', status: 'Inactive' },
{ id: '4', name: 'Diana Prince', email: 'diana@example.com', role: 'Editor', status: 'Active' },
];
export default function AdminDashboard() {
const [search, setSearch] = useState('');
const [users] = useState(INITIAL_USERS);
const filtered = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase()) ||
user.email.toLowerCase().includes(search.toLowerCase())
);
return (
<div className="flex min-h-screen bg-slate-50">
<aside className="w-64 bg-slate-900 text-white p-6 flex flex-col justify-between">
<div>
<span className="font-extrabold text-lg tracking-wider text-blue-400 block mb-10">TECHADMIN</span>
<nav className="space-y-3 text-xs font-semibold">
<a href="#" className="block p-3 bg-blue-600 rounded-xl text-white">📊 Analytics Overview</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">👥 User Management</a>
<a href="#" className="block p-3 text-slate-400 hover:bg-slate-800 rounded-xl transition">⚙️ System Settings</a>
</nav>
</div>
<div className="pt-4 border-t border-slate-800 text-[10px] text-slate-500">
Admin OS v2.1
</div>
</aside>
<main className="flex-1 p-10 max-w-6xl mx-auto space-y-8">
<div>
<h1 className="text-2xl font-bold text-slate-900">Analytics Overview</h1>
<p className="text-xs text-slate-500">Welcome back, System Administrator</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Total Revenue</span>
<span className="text-3xl font-extrabold text-slate-900">$48,295</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +14% from last month</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Active Users</span>
<span className="text-3xl font-extrabold text-slate-900">2,491</span>
<span className="text-[10px] text-green-600 font-bold block mt-2">▲ +5% from last week</span>
</div>
<div className="p-6 bg-white rounded-2xl border border-slate-200 shadow-xs">
<span className="text-xs text-slate-500 font-semibold mb-1 block">Server Load</span>
<span className="text-3xl font-extrabold text-slate-900">42%</span>
<span className="text-[10px] text-slate-500 block mt-2">Normal operating parameters</span>
</div>
</div>
<div className="bg-white p-8 rounded-3xl border border-slate-200 shadow-sm">
<div className="flex justify-between items-center mb-6">
<h2 className="font-bold text-lg text-slate-900">User Management Directory</h2>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user by name or email..."
className="p-2.5 border border-slate-200 rounded-xl text-xs w-72"
/>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr class="bg-slate-50 border-b border-slate-200 text-slate-600 font-bold">
<th className="p-3">Name</th>
<th className="p-3">Email</th>
<th className="p-3">Role</th>
<th className="p-3">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 text-slate-700">
{filtered.map(user => (
<tr key={user.id} className="hover:bg-slate-50">
<td className="p-3 font-semibold text-slate-900">{user.name}</td>
<td className="p-3 text-slate-500">{user.email}</td>
<td className="p-3 font-medium">{user.role}</td>
<td className="p-3">
<span className={`px-2 py-1 rounded-full text-[10px] font-bold ${user.status === 'Active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-600'}`}>
{user.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</main>
</div>
);
}Code Explanation
Implementation step
Common Errors
Use .toLowerCase() on both query and target strings.
Security & Performance
Test search input by typing user name to verify instant table row filtering.
Confirm metric summary values format cleanly.
Add chart.js integration.
Add user addition modal form.
Interview Questions
Q: Why use nested layouts?
A: Nested layouts keep sidebar navigation persistent across all dashboard sub-routes without re-rendering.