Stop scraping. Start building.
In this tutorial, we're going to build a simple but powerful dashboard that tracks TikTok user stats in real-time.
We'll use:
- Next.js (App Router)
- Tailwind CSS (Styling)
- SociaVault API (Data fetching)
- Recharts (Visualization)
By the end, you'll have a dashboard that takes a username (e.g., khaby.lame) and displays their follower growth, engagement rates, and top videos.
Prerequisites
- Node.js installed.
- A SociaVault API Key (Get a free one).
Step 1: Setup the Project
npx create-next-app@latest tiktok-dashboard
cd tiktok-dashboard
npm install recharts lucide-react axios
Step 2: Create the API Client
Create lib/api.ts. We'll use this to fetch data from SociaVault.
import axios from 'axios';
const API_KEY = process.env.NEXT_PUBLIC_SOCIAVAULT_KEY;
const BASE_URL = 'https://api.sociavault.com/v1/scrape/tiktok';
export const getProfile = async (handle: string) => {
const response = await axios.get(`${BASE_URL}/profile`, {
headers: { 'x-api-key': API_KEY },
params: { handle }
});
return response.data.data;
};
export const getVideos = async (handle: string) => {
const response = await axios.get(`${BASE_URL}/videos`, {
headers: { 'x-api-key': API_KEY },
params: { handle, amount: 10 }
});
return response.data.data.videos;
};
Step 3: Build the Dashboard Component
In app/page.tsx, we'll create a simple search form and display the results.
'use client';
import { useState } from 'react';
import { getProfile, getVideos } from '@/lib/api';
import { Search, Users, Heart, Play } from 'lucide-react';
export default function Dashboard() {
const [handle, setHandle] = useState('');
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(false);
const handleSearch = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const [profile, videos] = await Promise.all([
getProfile(handle),
getVideos(handle)
]);
setData({ profile, videos });
} catch (err) {
alert('Failed to fetch data');
}
setLoading(false);
};
return (
<div className="min-h-screen bg-gray-900 text-white p-8">
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">TikTok Analytics 📊</h1>
{/* Search Form */}
<form onSubmit={handleSearch} className="flex gap-4 mb-12">
<input
type="text"
value={handle}
onChange={(e) => setHandle(e.target.value)}
placeholder="Enter username (e.g. khaby.lame)"
className="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
disabled={loading}
className="bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded-lg font-medium transition-colors"
>
{loading ? 'Loading...' : 'Analyze'}
</button>
</form>
{data && (
<div className="space-y-8">
{/* Stats Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<StatCard
icon={<Users className="text-blue-400" />}
label="Followers"
value={data.profile.stats.followerCount}
/>
<StatCard
icon={<Heart className="text-red-400" />}
label="Total Likes"
value={data.profile.stats.heartCount}
/>
<StatCard
icon={<Play className="text-green-400" />}
label="Total Videos"
value={data.profile.stats.videoCount}
/>
</div>
{/* Recent Videos */}
<h2 className="text-xl font-semibold">Recent Videos</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{data.videos.map((video: any) => (
<div key={video.id} className="bg-gray-800 rounded-xl p-4 flex gap-4">
<img
src={video.cover}
alt="Cover"
className="w-24 h-32 object-cover rounded-lg"
/>
<div>
<p className="text-sm text-gray-400 mb-2">
{new Date(video.createTime * 1000).toLocaleDateString()}
</p>
<p className="line-clamp-2 mb-4">{video.desc}</p>
<div className="flex gap-4 text-sm text-gray-300">
<span className="flex items-center gap-1">
<Play size={14} /> {video.stats.playCount}
</span>
<span className="flex items-center gap-1">
<Heart size={14} /> {video.stats.diggCount}
</span>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
}
function StatCard({ icon, label, value }: any) {
return (
<div className="bg-gray-800 p-6 rounded-xl border border-gray-700">
<div className="flex items-center gap-3 mb-2">
{icon}
<span className="text-gray-400">{label}</span>
</div>
<div className="text-2xl font-bold">
{new Intl.NumberFormat('en-US', { notation: "compact" }).format(value)}
</div>
</div>
);
}
Why this is better than scraping
If you tried to build this by scraping TikTok directly with Puppeteer:
- You'd need a backend server (Next.js API routes have timeouts).
- You'd need to manage proxies.
- You'd get blocked after 5 requests.
By using SociaVault, we offloaded the entire data collection layer. Our Next.js app is just a UI shell.
Next Steps
You can extend this dashboard to:
- Calculate engagement rates (Likes / Views).
- Track follower growth over time (store data in Supabase).
- Compare two creators side-by-side.
Grab your API key here: SociaVault.com
Top comments (0)