DEV Community

Cover image for Build an Instagram Reels Analytics Dashboard with Next.js
Olamide Olaniyan
Olamide Olaniyan

Posted on

Build an Instagram Reels Analytics Dashboard with Next.js

Instagram's native insights are... okay. But if you manage multiple accounts or want to track competitors, they're useless. You can't export data, you can't track other people's engagement rates, and you definitely can't build custom reports.

In this tutorial, we're going to build a Custom Instagram Reels Dashboard that lets you:

  1. Track any public Instagram account (no login required).
  2. Visualize views, likes, and comments over time.
  3. Calculate "Viral Score" for every Reel.

We'll use Next.js 14, Tailwind CSS, Recharts, and the SociaVault API to fetch the data.

The Stack

  • Framework: Next.js 14 (App Router)
  • Styling: Tailwind CSS
  • Charts: Recharts
  • Data: SociaVault API (Instagram Scraper)

Step 1: Project Setup

First, create a new Next.js project:

npx create-next-app@latest instagram-dashboard
cd instagram-dashboard
npm install recharts lucide-react
Enter fullscreen mode Exit fullscreen mode

Step 2: The API Client

We need a way to fetch data from Instagram. Since Instagram's official Graph API is a nightmare of permissions and approvals, we'll use SociaVault's API, which works out of the box for public data.

Create lib/api.ts:

const API_KEY = process.env.SOCIAVAULT_API_KEY;
const BASE_URL = "https://api.sociavault.com/api/v1";

export async function getProfileReels(username: string) {
  const response = await fetch(
    `${BASE_URL}/instagram/user/${username}/reels?limit=50`,
    {
      headers: {
        "x-api-key": API_KEY!,
        "Content-Type": "application/json",
      },
      next: { revalidate: 3600 }, // Cache for 1 hour
    }
  );

  if (!response.ok) throw new Error("Failed to fetch reels");
  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Calculating Metrics

Raw data is boring. We want insights. Let's create a utility to calculate engagement rates and viral scores.

Create lib/metrics.ts:

export function calculateMetrics(reels: any[]) {
  return reels.map((reel) => {
    const engagement = reel.like_count + reel.comment_count;
    const engagementRate = (engagement / reel.view_count) * 100;

    // Simple viral score: High views + High engagement
    const viralScore = (reel.view_count / 1000) + (engagementRate * 10);

    return {
      ...reel,
      engagement,
      engagementRate: engagementRate.toFixed(2),
      viralScore: Math.round(viralScore),
    };
  });
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Building the Dashboard Component

Now for the UI. We'll create a dashboard that shows a performance chart and a list of top Reels.

components/Dashboard.tsx:

"use client";

import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { Play, Heart, MessageCircle } from 'lucide-react';

export default function Dashboard({ data }: { data: any[] }) {
  return (
    <div className="p-6 space-y-8">
      {/* Performance Chart */}
      <div className="bg-white p-6 rounded-xl shadow-sm border">
        <h2 className="text-lg font-semibold mb-4">Views vs. Engagement</h2>
        <div className="h-[300px]">
          <ResponsiveContainer width="100%" height="100%">
            <BarChart data={data.slice(0, 10).reverse()}>
              <XAxis dataKey="taken_at" tickFormatter={(val) => new Date(val).toLocaleDateString()} />
              <YAxis />
              <Tooltip />
              <Bar dataKey="view_count" fill="#3b82f6" radius={[4, 4, 0, 0]} />
            </BarChart>
          </ResponsiveContainer>
        </div>
      </div>

      {/* Top Reels Grid */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        {data.map((reel) => (
          <div key={reel.id} className="bg-white p-4 rounded-xl border hover:shadow-md transition">
            <div className="relative aspect-[9/16] bg-gray-100 rounded-lg mb-3 overflow-hidden">
              <img src={reel.thumbnail_url} alt="Reel cover" className="object-cover w-full h-full" />
              <div className="absolute bottom-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs flex items-center">
                <Play className="w-3 h-3 mr-1" />
                {(reel.view_count / 1000).toFixed(1)}k
              </div>
            </div>
            <div className="flex justify-between text-sm text-gray-600">
              <span className="flex items-center">
                <Heart className="w-4 h-4 mr-1" /> {reel.like_count}
              </span>
              <span className="flex items-center">
                <MessageCircle className="w-4 h-4 mr-1" /> {reel.comment_count}
              </span>
              <span className="font-medium text-blue-600">
                {reel.engagementRate}% ER
              </span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Putting It Together

Finally, let's connect it all in our page.

app/dashboard/[username]/page.tsx:

import { getProfileReels } from "@/lib/api";
import { calculateMetrics } from "@/lib/metrics";
import Dashboard from "@/components/Dashboard";

export default async function Page({ params }: { params: { username: string } }) {
  const rawData = await getProfileReels(params.username);
  const metrics = calculateMetrics(rawData.items);

  return (
    <main className="min-h-screen bg-gray-50">
      <div className="max-w-7xl mx-auto py-12">
        <h1 className="text-3xl font-bold px-6 mb-2">@{params.username}</h1>
        <p className="text-gray-500 px-6 mb-8">Reels Performance Analytics</p>
        <Dashboard data={metrics} />
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In about 15 minutes, we built a tool that gives us more insight than Instagram's native app. You can extend this by:

  • Adding a "Download CSV" button.
  • Tracking follower growth over time.
  • Comparing two accounts side-by-side.

The best part? You own the data.

Resources:

Top comments (0)