Next.js
Mini Project – Dashboard
Build a complete admin dashboard with real-time data, charts, and interactive components using Next.js App Router.
Dashboards are everywhere. GitHub shows repository stats. Vercel displays deployment metrics. Your bank shows account summaries. A dashboard takes raw data and makes it visual, actionable, and easy to understand. The NewsWave team wants an admin dashboard to monitor their content. They need to see article performance, user engagement, and publishing metrics. This means combining server-side data fetching with client-side interactivity. Unlike a simple blog or static site, dashboards require dynamic data loading, state management, and real-time updates. You'll build components that fetch data on the server, then add client-side features like filtering and sorting.Dashboard Architecture
Think of a dashboard as a mission control center. Each panel shows different information, but they all work together. Some data updates frequently (like page views), while other data stays stable (like total articles published). Your dashboard needs multiple data sources. Article statistics come from one API endpoint. User metrics come from another. Traffic data might come from a third source. Each piece loads independently so users see information as it becomes available. The architecture follows a hub-and-spoke pattern. The main dashboard page acts as the hub, coordinating data from multiple spokes (individual API routes). Each spoke handles one specific type of data.Server Components
Initial data loading, SEO optimization, fast first paint
Client Components
Interactive filtering, real-time updates, user actions
API Routes
Data fetching, business logic, database queries
Data Sources
Mock APIs, external services, cached responses
Project Structure Setup
Dashboards need organized file structures. You'll separate data logic from presentation logic. Each dashboard section gets its own component file, and each data type gets its own API route. Start by creating the folder structure. Thedashboard folder will contain all admin-related pages. The components folder gets dashboard-specific components.
# Create the dashboard structure
mkdir -p app/dashboard
mkdir -p components/dashboard
mkdir -p app/api/dashboardWhat just happened?
You created three main folders: app/dashboard for pages, components/dashboard for UI components, and app/api/dashboard for data endpoints. This separation keeps related code together while maintaining clear boundaries.
Dashboard API Routes
API routes provide the data your dashboard displays. Unlike static data, dashboard information changes frequently. Articles get published, users read content, and metrics update throughout the day. Each API route returns JSON data that components can consume. The route handles data processing, filtering, and formatting. Components just display what the API provides. Create the stats API route first. This endpoint will return article performance data like view counts, published articles, and engagement metrics.// app/api/dashboard/stats/route.js
// Returns article statistics for NewsWave dashboard
export async function GET() {
// Mock data - in real app, query your database
const stats = {
totalArticles: 1247, // Total published articles
totalViews: 89543, // Total page views this month
activeUsers: 2834, // Users who read articles this week
avgReadTime: 4.2 // Average minutes per article
};
return Response.json(stats); // Send stats as JSON response
}What just happened?
You created an API route that returns dashboard statistics. The GET function responds to HTTP GET requests with JSON data. Try this: Visit the API endpoint directly in your browser to see the raw JSON response.
// app/api/dashboard/articles/route.js
// Returns recent articles with performance metrics
export async function GET() {
// Mock recent articles data
const articles = [
{
id: 1,
title: "AI Breakthrough in Language Models",
author: "Sarah Chen",
publishedAt: "2024-01-15T09:30:00Z", // ISO date string
views: 2847, // Individual article views
category: "Tech", // Article category
status: "published" // Publishing status
},
{
id: 2,
title: "Global Climate Summit Results",
author: "Mike Rodriguez",
publishedAt: "2024-01-14T14:20:00Z",
views: 1923,
category: "World",
status: "published"
}
];
return Response.json({ articles, total: articles.length });
}What just happened?
You created an articles API that returns structured data with metadata. Each article includes performance metrics and publication info. The response wraps articles in an object with a total count for pagination. Try this: Add more mock articles and see how the total count updates automatically.
Dashboard Components
Dashboard components display data in digestible formats. Raw numbers like "89543 views" mean little without context. A good component shows the number, adds visual emphasis, and provides comparison context. Stats cards are the building blocks of dashboards. Each card displays one key metric with formatting that makes the number easy to understand. Think GitHub's repository stats or Vercel's deployment metrics. Create a reusable stats card component. This component will display metrics like total articles, page views, and user engagement with proper formatting and visual hierarchy.// components/dashboard/StatsCard.js
// Displays a single metric with formatting and context
export default function StatsCard({ title, value, change, icon }) {
// Format large numbers with commas (1234 becomes 1,234)
const formatNumber = (num) => {
return new Intl.NumberFormat().format(num);
};
return (
<div className="stats-card">
<div className="stat-header">
<span className="stat-icon">{icon}</span> {/* Visual icon */}
<h3 className="stat-title">{title}</h3> {/* Metric name */}
</div>
<div className="stat-value">{formatNumber(value)}</div> {/* Main number */}
{change && (
<div className="stat-change">
{change > 0 ? '↗' : '↘'} {Math.abs(change)}% {/* Trend indicator */}
</div>
)}
</div>
);
}What just happened?
You created a reusable stats card that formats numbers with commas and shows trend indicators. The component accepts props for title, value, change percentage, and icon. Each card displays its data in a clean, scannable format. Try this: Pass different values to see how number formatting and trend arrows change automatically.
// components/dashboard/DataTable.js
// Table component for displaying article data with sorting
'use client'; // This component needs client-side interactivity
import { useState } from 'react';
export default function DataTable({ articles }) {
const [sortField, setSortField] = useState('views'); // Current sort column
const [sortOrder, setSortOrder] = useState('desc'); // Sort direction
// Sort articles based on current field and order
const sortedArticles = [...articles].sort((a, b) => {
if (sortOrder === 'asc') {
return a[sortField] > b[sortField] ? 1 : -1;
}
return a[sortField] < b[sortField] ? 1 : -1;
});
return (
<table className="data-table">
<thead>
<tr>
<th onClick={() => setSortField('title')}>Title</th>
<th onClick={() => setSortField('author')}>Author</th>
<th onClick={() => setSortField('views')}>Views</th>
</tr>
</thead>
</table>
);
}What just happened?
You created an interactive table with client-side sorting. The 'use client' directive tells Next.js this component needs browser JavaScript. Clicking column headers sorts the data without server requests. Try this: Click different column headers to see the sorting change instantly.
Main Dashboard Page
The main dashboard page brings everything together. It fetches data from your API routes, then passes that data to your components. The page combines server-side data loading with client-side interactivity. Dashboard pages typically use a grid layout. Key metrics appear at the top in stats cards. Detailed information appears below in tables and charts. This creates a visual hierarchy that guides user attention. Create the main dashboard page. This page will fetch stats and articles data on the server, then render your components with that data.// app/dashboard/page.js
// Main dashboard page that combines stats and article data
import StatsCard from '@/components/dashboard/StatsCard';
import DataTable from '@/components/dashboard/DataTable';
// Fetch dashboard data from internal API routes
async function getDashboardData() {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
// Fetch stats and articles in parallel for better performance
const [statsRes, articlesRes] = await Promise.all([
fetch(`${baseUrl}/api/dashboard/stats`), // Get overall statistics
fetch(`${baseUrl}/api/dashboard/articles`) // Get recent articles
]);
const stats = await statsRes.json(); // Parse stats JSON
const articles = await articlesRes.json(); // Parse articles JSON
return { stats, articles: articles.articles };
}What just happened?
You created a data fetching function that calls your API routes in parallel. Promise.all runs both requests simultaneously instead of waiting for each one. This cuts loading time in half. Try this: Add console logs to see the order of data loading.
// app/dashboard/page.js (continued)
// Dashboard page component that renders the complete interface
export default async function DashboardPage() {
const { stats, articles } = await getDashboardData(); // Load data on server
return (
<div className="dashboard-container">
<header className="dashboard-header">
<h1>NewsWave Dashboard</h1> {/* Page title */}
<p>Monitor your content performance and user engagement</p>
</header>
<section className="stats-grid">
<StatsCard
title="Total Articles"
value={stats.totalArticles}
change={12}
icon="📰"
/>
<StatsCard
title="Page Views"
value={stats.totalViews}
change={-3}
icon="👁️"
/>
<StatsCard
title="Active Users"
value={stats.activeUsers}
change={8}
icon="👥"
/>
</section>
<section className="articles-section">
<h2>Recent Articles</h2> {/* Table section title */}
<DataTable articles={articles} /> {/* Interactive table */}
</section>
</div>
);
}What just happened?
You created a complete dashboard page that combines server-side data fetching with client-side interactivity. The page loads data once on the server, then passes it to components for rendering. Stats cards show key metrics while the data table provides detailed article information with sorting. Try this: Refresh the page to see how server-side rendering provides instant content.
Real-time Updates
Static dashboards become stale quickly. Real applications need fresh data without full page reloads. You can add real-time updates using client-side data fetching and periodic refreshes. The pattern involves moving from server-side data loading to client-side data loading for components that need frequent updates. Server components handle initial data loading for SEO and performance. Client components handle data refreshing for interactivity. Create a real-time stats component that updates every 30 seconds. This component will fetch fresh data from your API and update the display without disrupting user interaction.// components/dashboard/LiveStats.js
// Stats component that updates automatically every 30 seconds
'use client';
import { useState, useEffect } from 'react';
import StatsCard from './StatsCard';
export default function LiveStats({ initialStats }) {
const [stats, setStats] = useState(initialStats); // Current stats data
const [lastUpdated, setLastUpdated] = useState(new Date()); // Last refresh time
useEffect(() => {
// Function to fetch fresh stats from API
const updateStats = async () => {
try {
const response = await fetch('/api/dashboard/stats');
const newStats = await response.json();
setStats(newStats); // Update state with fresh data
setLastUpdated(new Date()); // Record update time
} catch (error) {
console.error('Failed to update stats:', error);
}
};
// Set up interval to update every 30 seconds
const interval = setInterval(updateStats, 30000); // 30000ms = 30 seconds
return () => clearInterval(interval); // Cleanup on unmount
}, []);
return (
<div>
<div style={{ marginBottom: '16px', fontSize: '14px', color: '#6b7280' }}>
Last updated: {lastUpdated.toLocaleTimeString()} {/* Show update time */}
</div>
<div className="stats-grid">
<StatsCard title="Total Articles" value={stats.totalArticles} icon="📰" />
<StatsCard title="Page Views" value={stats.totalViews} icon="👁️" />
<StatsCard title="Active Users" value={stats.activeUsers} icon="👥" />
</div>
</div>
);
}What just happened?
You created a self-updating component using setInterval and useEffect. The component fetches fresh data every 30 seconds and updates the display automatically. The cleanup function prevents memory leaks by clearing the interval when the component unmounts. Try this: Watch the timestamp update to see the real-time refresh in action.
Project Summary
You've built a complete admin dashboard with multiple moving parts working together. The dashboard demonstrates key Next.js patterns: server-side data fetching, client-side interactivity, API routes, and component composition. Your dashboard includes server components for initial data loading, client components for user interaction, API routes for data provisioning, and real-time updates for fresh information. This architecture scales well as you add more features and data sources. The project shows how Next.js handles both static and dynamic content in the same application. Static elements render on the server for performance. Dynamic elements hydrate on the client for interactivity. Both approaches work together seamlessly.Deployment Considerations
Real dashboards need environment variables for API endpoints, database connections for persistent data, authentication for user access, and caching strategies for performance. Consider using React Query or SWR for better data fetching patterns in production applications.
Quiz
1. What is the recommended architecture pattern for Next.js dashboards?
2. How should you fetch data from multiple API endpoints in parallel for a dashboard?
3. What's required to implement real-time updates in a Next.js dashboard component?
Up Next: Mini Project – Ecommerce
Build a complete online store with product catalogs, shopping carts, and checkout flows using Next.js App Router.