HomeBlogTutorial
TUTORIAL

Building a Next.js Dashboard with Charts (Complete Guide)

Learn how to build a production-ready analytics dashboard in Next.js 14 with real-time charts, KPI cards, and responsive layouts.

UIChart.com·May 28, 2025·10 min read

What We're Building

A complete analytics dashboard with KPI metric cards, a weekly visitors area chart, a top sources bar chart, and a device breakdown donut chart. This is the kind of dashboard you'd ship to a real client.

Project Setup

npx create-next-app@latest my-dashboard --typescript --tailwind --app
cd my-dashboard
npm install recharts

Step 1: Create the Data Layer

In a real app this would come from an API. For now we'll use static data:

// lib/data.ts
export const kpiData = [
  { label: 'Total Revenue', value: '$48,291', change: '+12.4%', up: true },
  { label: 'Active Users',  value: '24,891',  change: '+8.1%',  up: true },
  { label: 'Bounce Rate',   value: '38.2%',   change: '-3.2%',  up: false },
  { label: 'Avg Session',   value: '4m 12s',  change: '+0.8%',  up: true },
];

export const weeklyData = [
  { day: 'Mon', visitors: 1200 },
  { day: 'Tue', visitors: 1900 },
  { day: 'Wed', visitors: 1500 },
  { day: 'Thu', visitors: 2800 },
  { day: 'Fri', visitors: 2400 },
  { day: 'Sat', visitors: 3100 },
  { day: 'Sun', visitors: 2700 },
];

Step 2: KPI Cards Component

'use client';
import { kpiData } from '@/lib/data';

export default function KPICards() {
  return (
    <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
      {kpiData.map((kpi) => (
        <div key={kpi.label} className="bg-gray-900 border border-gray-800 rounded-xl p-5">
          <p className="text-sm text-gray-500 mb-2">{kpi.label}</p>
          <p className="text-2xl font-bold mb-1">{kpi.value}</p>
          <p className={kpi.up ? 'text-green-400 text-sm' : 'text-red-400 text-sm'}>
            {kpi.up ? '↑' : '↓'} {kpi.change}
          </p>
        </div>
      ))}
    </div>
  );
}

Step 3: Weekly Visitors Chart

'use client';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { weeklyData } from '@/lib/data';

export default function VisitorsChart() {
  return (
    <div className="bg-gray-900 border border-gray-800 rounded-xl p-5">
      <h3 className="font-semibold mb-4">Weekly Visitors</h3>
      <ResponsiveContainer width="100%" height={240}>
        <AreaChart data={weeklyData}>
          <defs>
            <linearGradient id="visitors" x1="0" y1="0" x2="0" y2="1">
              <stop offset="5%" stopColor="#00e5a0" stopOpacity={0.3} />
              <stop offset="95%" stopColor="#00e5a0" stopOpacity={0} />
            </linearGradient>
          </defs>
          <CartesianGrid strokeDasharray="3 3" stroke="#1e2328" />
          <XAxis dataKey="day" stroke="#5c6370" />
          <YAxis stroke="#5c6370" />
          <Tooltip />
          <Area dataKey="visitors" stroke="#00e5a0" fill="url(#visitors)" strokeWidth={2} />
        </AreaChart>
      </ResponsiveContainer>
    </div>
  );
}

Step 4: Assemble the Dashboard Page

// app/dashboard/page.tsx
import KPICards from '@/components/KPICards';
import VisitorsChart from '@/components/VisitorsChart';

export default function DashboardPage() {
  return (
    <div className="max-w-7xl mx-auto p-6 space-y-6">
      <h1 className="text-2xl font-bold">Analytics Overview</h1>
      <KPICards />
      <VisitorsChart />
    </div>
  );
}

Step 5: Add Real Data with Server Components

Next.js 14 makes it easy to fetch data on the server and pass it to client chart components:

// app/dashboard/page.tsx (Server Component)
async function getDashboardData() {
  const res = await fetch('https://your-api.com/analytics', {
    next: { revalidate: 60 } // revalidate every 60s
  });
  return res.json();
}

export default async function DashboardPage() {
  const data = await getDashboardData();
  return <VisitorsChart data={data.weekly} />;
}

Performance Tips

  • Always add 'use client' to chart components — Recharts uses browser APIs
  • Use dynamic() imports with ssr: false for charts in SSR pages to avoid hydration issues
  • Set isAnimationActive={false} on charts that update frequently (real-time data)
  • Wrap charts in Suspense boundaries with skeleton loaders for better UX
#nextjs#dashboard#tutorial#recharts#analytics
Related Articles
How to Create a Bar Chart in React (Step by Step)
6 min read · Tutorial
Recharts Tutorial for Beginners — Everything You Need to Know
9 min read · Tutorial