OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 65 / 90
stack-analysis
CHAPTER 65 / 90
읽기 약 2
FUNCTION

대시보드: Recharts + 실시간 데이터


핵심 개념

Recharts·Tremor·실시간 업데이트·드릴다운 — 분석 대시보드 구축.

본문

Recharts vs 대안

📋 코드 (13줄)
| 라이브러리 | 번들 | 학습 | 적합 |
|---|---|---|---|
| Recharts | 90KB | ⭐⭐ | 일반 차트 |
| Tremor | 200KB | ⭐ | 대시보드 통합 |
| Chart.js | 80KB | ⭐⭐ | 단순 |
| Visx | 가변 | ⭐⭐⭐⭐ | 커스텀 |
| ECharts | 1MB | ⭐⭐⭐ | 복잡 비주얼 |


결론:
- 일반 대시보드 → Recharts
- 빠른 셋업 → Tremor
- 픽셀 단위 커스텀 → Visx

Recharts 기본

TSX📋 코드 (43줄)
import {
  LineChart, Line, BarChart, Bar,
  XAxis, YAxis, CartesianGrid, Tooltip, Legend,
  ResponsiveContainer,
} from 'recharts';


function RevenueChart({ data }: { data: { date: string; revenue: number; orders: number }[] }) {
  return (
    <ResponsiveContainer width="100%" height={300}>
      <LineChart data={data}>
        <CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
        <XAxis dataKey="date" />
        <YAxis yAxisId="left" />
        <YAxis yAxisId="right" orientation="right" />
        <Tooltip
          formatter={(v: number, name: string) => {
            if (name === 'revenue') return [`₩${v.toLocaleString()}`, '매출'];
            return [v, '주문 수'];
          }}
        />
        <Legend />
        <Line
          yAxisId="left"
          type="monotone"
          dataKey="revenue"
          stroke="#6366f1"
          strokeWidth={2}
          dot={false}
          activeDot={{ r: 6 }}
        />
        <Line
          yAxisId="right"
          type="monotone"
          dataKey="orders"
          stroke="#10b981"
          strokeWidth={2}
          dot={false}
        />
      </LineChart>
    </ResponsiveContainer>
  );
}

Tremor — 대시보드 카드

TSX📋 코드 (30줄)
import { Card, Metric, Text, ProgressBar, Flex } from '@tremor/react';

function StatsCard({ label, value, target, change }: any) {
  const pct = (value / target) * 100;
  return (
    <Card>
      <Text>{label}</Text>
      <Metric>{value.toLocaleString()}</Metric>
      <Flex className="mt-4">
        <Text className="truncate">{pct.toFixed(0)}% of target</Text>
        <Text>{target.toLocaleString()}</Text>
      </Flex>
      <ProgressBar value={pct} className="mt-2" />
      <Text className={`mt-2 ${change > 0 ? 'text-green-600' : 'text-red-600'}`}>
        {change > 0 ? '↑' : '↓'} {Math.abs(change)}%
      </Text>
    </Card>
  );
}


function Dashboard() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      <StatsCard label="MRR" value={45230} target={50000} change={12.5} />
      <StatsCard label="신규 가입" value={234} target={300} change={-5.2} />
      <StatsCard label="해지율" value={2.3} target={3.0} change={-0.5} />
    </div>
  );
}

실시간 업데이트 (TanStack Query + 폴링)

TSX📋 코드 (20줄)
import { useQuery } from '@tanstack/react-query';

function LiveDashboard() {
  const { data } = useQuery({
    queryKey: ['dashboard', 'today'],
    queryFn: async () => {
      const res = await fetch('/api/dashboard/today');
      return res.json();
    },
    refetchInterval: 5000,  // 5초마다 갱신
    refetchOnWindowFocus: true,
  });

  return (
    <>
      <RevenueChart data={data?.revenue ?? []} />
      <RealtimeOrders count={data?.activeOrders ?? 0} />
    </>
  );
}

실시간 (WebSocket)

TSX📋 코드 (26줄)
function RealtimeChart() {
  const [points, setPoints] = useState<DataPoint[]>([]);

  useEffect(() => {
    const channel = supabase.channel('events:dashboard')
      .on('postgres_changes', {
        event: 'INSERT',
        schema: 'public',
        table: 'orders',
      }, (payload) => {
        setPoints(prev => [...prev.slice(-49), {
          time: new Date(),
          value: payload.new.amount,
        }]);
      })
      .subscribe();

    return () => { supabase.removeChannel(channel); };
  }, []);

  return (
    <LineChart data={points} width={600} height={300}>
      <Line dataKey="value" stroke="#6366f1" isAnimationActive={false} />
    </LineChart>
  );
}

드릴다운 (클릭 → 상세)

TSX📋 코드 (31줄)
function DrillDownChart() {
  const [period, setPeriod] = useState<{ year: number; month?: number; day?: number }>({
    year: 2026,
  });

  const { data } = useQuery({
    queryKey: ['revenue', period],
    queryFn: () => fetch(`/api/revenue?${new URLSearchParams(period as any)}`).then(r => r.json()),
  });

  const handleClick = (entry: any) => {
    if (!period.month) {
      setPeriod({ ...period, month: entry.month });
    } else if (!period.day) {
      setPeriod({ ...period, day: entry.day });
    }
  };

  return (
    <>
      <Breadcrumb>
        <button onClick={() => setPeriod({ year: period.year })}>{period.year}</button>
        {period.month && <span> / {period.month}월</span>}
        {period.day && <span> / {period.day}일</span>}
      </Breadcrumb>
      <BarChart data={data} onClick={handleClick}>
        <Bar dataKey="revenue" fill="#6366f1" cursor="pointer" />
      </BarChart>
    </>
  );
}

CSV 내보내기

TYPESCRIPT📋 코드 (15줄)
function exportCSV(data: any[], filename: string) {
  const headers = Object.keys(data[0]);
  const rows = data.map(row =>
    headers.map(h => `"${String(row[h]).replace(/"/g, '""')}"`).join(',')
  );
  const csv = [headers.join(','), ...rows].join('\n');

  const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv' });  // BOM for Excel
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

다음 챕터

CH.66 "검색: 전문 검색(Full-text) + 필터링".


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 프로젝트의 대시보드 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

대시보드 관련 실제 서비스 5개를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 코드베이스에서 대시보드
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 풀스택 시장의
대시보드 트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
대시보드: Recharts + 실시간 데이터 이 3가지만 확실히 잡으세요
1.Recharts = 일반 차트 표준 — 90KB·SVG·SSR 호환
2.Tremor로 대시보드 카드·메트릭 빠른 셋업
3.TanStack Query + refetchInterval = 폴링, WebSocket = 실시간 푸시


공유하기
진행도 65 / 90