How to Build a Token Analytics Dashboard for Web3 Projects
Build a token analytics dashboard with price charts, holder tables, and transaction feeds. Data sources, Next.js architecture, and performance tips with TypeScript code examples.
What a token analytics dashboard needs
Token analytics dashboards answer: what is this token worth, who holds it, how active is trading, and where is liquidity? Core features include live price and volume, holder distribution, transaction feed, liquidity pool data, and market cap metrics.
These dashboards serve token projects, investors, and researchers — each audience needs different depth. Start with price, volume, and holder count. Add liquidity and transaction feeds as you scale.
Data sources
Combine multiple sources for complete coverage. No single API provides everything reliably.
**Price and market data** — CoinGecko API or DeFiLlama for price, volume, market cap, and historical charts. Cache aggressively (30–60s).
**On-chain holders and transfers** — Etherscan API, Alchemy Transfers API, or a custom subgraph for holder counts and distribution.
**Liquidity** — DEX subgraphs (Uniswap, SushiSwap) for pool reserves, volume, and fee data.
**Direct RPC** — viem/wagmi for real-time contract reads: totalSupply, decimals, name, symbol.
// lib/fetchers/token-data.ts
import { createPublicClient, http, erc20Abi } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({
chain: mainnet,
transport: http(process.env.ALCHEMY_SERVER_URL),
});
export async function fetchOnChainTokenData(address: `0x${string}`) {
const [name, symbol, decimals, totalSupply] = await client.multicall({
contracts: [
{ address, abi: erc20Abi, functionName: 'name' },
{ address, abi: erc20Abi, functionName: 'symbol' },
{ address, abi: erc20Abi, functionName: 'decimals' },
{ address, abi: erc20Abi, functionName: 'totalSupply' },
],
});
return {
name: name.result as string,
symbol: symbol.result as string,
decimals: decimals.result as number,
totalSupply: totalSupply.result as bigint,
};
}
export async function fetchMarketData(coingeckoId: string) {
const res = await fetch(
`https://api.coingecko.com/api/v3/coins/${coingeckoId}?localization=false&tickers=false&community_data=false`
);
return res.json();
}Frontend architecture
Use Next.js App Router with server components for initial data and client components for live updates.
// app/tokens/[address]/page.tsx
import { fetchOnChainTokenData, fetchMarketData } from '@/lib/fetchers/token-data';
import { TokenHeader } from '@/components/token/TokenHeader';
import { PriceChart } from '@/components/token/PriceChart';
import { HolderTable } from '@/components/token/HolderTable';
import { TransactionFeed } from '@/components/token/TransactionFeed';
export default async function TokenPage({ params }: { params: { address: string } }) {
const onChain = await fetchOnChainTokenData(params.address as `0x${string}`);
const market = await fetchMarketData(onChain.coingeckoId);
return (
<main className="mx-auto max-w-6xl space-y-8 p-6">
<TokenHeader token={onChain} market={market} />
<PriceChart coingeckoId={onChain.coingeckoId} />
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
<HolderTable address={params.address} />
<TransactionFeed address={params.address} />
</div>
</main>
);
}Chart and table UI patterns
Price charts: TradingView Lightweight Charts for candlesticks, Recharts for area/line charts. Fixed 400px height container. Show 24h/7d/30d/1y time range tabs.
Holder tables: sortable columns (address, balance, % of supply). Truncate addresses with copy button. Paginate at 50 rows. Link to block explorer.
// components/token/HolderTable.tsx
'use client';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
export function HolderTable({ address }: { address: string }) {
const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
queryKey: ['holders', address, page],
queryFn: () => fetchHolders(address, page),
staleTime: 300_000,
});
if (isLoading) return <TableSkeleton rows={10} />;
return (
<div className="overflow-x-auto rounded-xl border border-white/10">
<table className="w-full text-sm">
<thead className="border-b border-white/10 bg-white/5">
<tr>
<th className="p-3 text-left">#</th>
<th className="p-3 text-left">Address</th>
<th className="p-3 text-right">Balance</th>
<th className="p-3 text-right">% Supply</th>
</tr>
</thead>
<tbody>
{data?.holders.map((holder, i) => (
<tr key={holder.address} className="border-b border-white/5">
<td className="p-3 text-gray-400">{(page - 1) * 50 + i + 1}</td>
<td className="p-3 font-mono">{truncateAddress(holder.address)}</td>
<td className="p-3 text-right">{formatTokenAmount(holder.balance)}</td>
<td className="p-3 text-right">{holder.percentage.toFixed(2)}%</td>
</tr>
))}
</tbody>
</table>
</div>
);
}Performance tips
Server-render token metadata and initial price at build/request time. Client-side polling only for live price (30s interval). Virtualize holder tables over 100 rows. Cache API responses at the edge. Batch CoinGecko price requests (up to 250 IDs per call).
Security and reliability
Validate contract addresses with checksum formatting. Show data source and last-updated timestamp. Handle delisted or scam tokens gracefully. Rate-limit your API routes. Never trust user-submitted contract addresses without validation.
import { isAddress, getAddress } from 'viem';
export function validateTokenAddress(input: string): `0x${string}` | null {
if (!isAddress(input)) return null;
return getAddress(input);
}FAQ
**Which chart library for token price history?** TradingView Lightweight Charts for professional candlesticks. Recharts for simpler area charts with less setup.
**How do I get holder data?** Etherscan token holder endpoint for quick MVPs. Custom subgraphs for accurate real-time holder tracking at scale.
**Should I show unverified tokens?** Yes, but with a clear warning badge. Users search for new tokens immediately after launch.
**How do I handle multi-chain tokens?** Same contract address on different chains may be different tokens. Always show the chain name alongside the address.
Want to work together? I build Web3 dashboards and DeFi interfaces.