TuBrief
구독 채널
비디오
커뮤니티

Recharts로 뚱뚱해진 대시보드 번들, TanStack Charts로 빼는 법

TuBrief 편집팀
2026년 8월 12일
0
컴퓨터/소프트웨어

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

한국어EnglishEspañol中文العربيةहिन्दीDeutschFrançaisPortuguêsРусскийBahasa Indonesia日本語

관련 영상

TanStack이 또 해냈습니다... 이번엔 차트입니다8:14

TanStack이 또 해냈습니다... 이번엔 차트입니다

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Recharts로 뚱뚱해진 대시보드 번들, TanStack Charts로 빼는 법

대시보드 페이지 하나 들어갔는데 차트 로딩하느라 모래시계가 돈다면 십중팔구 Recharts 문제다. 차트 몇 개 얹었다고 번들 크기가 수백 킬로바이트씩 불어나는 걸 보면 한숨부터 나온다. Recharts는 React DOM 조율 주기에 너무 바짝 붙어 있다. UI 구조와 데이터 처리가 엉켜 있어서 가볍게 쓰기가 힘들다.

이럴 때 TanStack Charts가 괜찮은 대안이 된다. 시각화 로직을 그래픽 문법 기반 코어로 따로 떼어냈다. Recharts가 보통 150KiB 넘게 먹는 반면, TanStack Charts는 40KiB 안팎이다. 번들 크기가 70% 가까이 빠진다.

문제는 프로덕션 코드 마이그레이션이다. 잘 돌아가는 화면을 엎는 건 언제나 찜찜하다. 팀원들을 설득하고 고장 없이 전환하려면 명확한 순서가 필요하다.

Recharts 코드 안전하게 걷어내기

한 번에 다 바꾸려고 하면 어디서 터졌는지 찾기도 힘들다. 컴포넌트 하나를 잡고 딱 4단계로 쪼개서 넘어가야 한다.

  1. 데이터 전처리 분리: Recharts 컴포넌트 JSX 안에 섞여 있던 데이터 가공 로직부터 순수 함수로 밖으로 빼낸다.
  2. 마크 매핑: 축 설정과 선, 점 같은 시각 요소들을 defineChart 선언 객체의 marks 배열로 옮긴다.
  3. 유틸리티 교체: ResponsiveContainer와 Tooltip을 TanStack의 자동 스케일러와 @tanstack/charts/tooltip 모듈로 바꾼다.
  4. 메모이제이션: 차트 정의 객체를 useMemo로 감싸고 최상위 Chart 컴포넌트에 넘긴다.

이 순서만 지키면 첫 차트 컴포넌트 교체에 걸리는 시간을 절반으로 줄일 수 있다.

import React, { useMemo } from 'react';
import { defineChart, lineY, dot } from '@tanstack/charts';
import { scaleBand } from '@tanstack/charts/scales/band';
import { scaleLinear } from '@tanstack/charts/scales/linear';
import { tooltip } from '@tanstack/charts/tooltip';
import { Chart } from '@tanstack/charts/react';

interface DataRow {
  date: string;
  value: number;
}

export function MigratedRevenueChart({ data }: { data: DataRow[] }) {
  const definition = useMemo(() => {
    return defineChart({
      marks: [
        lineY(data, {
          x: 'date',
          y: 'value',
          stroke: 'var(--ts-chart-1, #2563eb)',
          strokeWidth: 2,
        }),
        dot(data, {
          x: 'date',
          y: 'value',
          fill: 'var(--ts-chart-1, #2563eb)',
          r: 4,
        }),
      ],
      x: {
        scale: () => scaleBand<string>().padding(0.2),
      },
      y: {
        scale: scaleLinear,
        nice: true,
        grid: true,
      },
      tooltip: {
        use: tooltip,
        className: 'custom-tooltip',
      },
    });
  }, [data]);

  return (
    <div style={{ width: '100%', height: 300 }}>
      <Chart definition={definition} />
    </div>
  );
}

다크모드 대응 시 자바스크립트 재렌더링 없애기

다크모드 토글할 때마다 차트 컴포넌트 전체가 다시 그리는 건 촌스럽다. TanStack Charts는 하드코딩된 색상 값 대신 CSS 커스텀 프로퍼티와 currentColor를 그대로 지원한다.

차트 색상을 자바스크립트 상태로 관리하지 말고 CSS 변수로 밀어 넣자. 차트 기본 팔레트는 --ts-chart-1부터 --ts-chart-6까지 참조하게 만들면 된다.

  1. CSS 루트와 [data-theme='dark']에 차트용 변수를 미리 정의한다.
  2. defineChart 옵션의 stroke나 fill에 var(--ts-chart-1) 형태의 문자열을 넣는다.
  3. 툴팁 스타일도 CSS 클래스로 관리하고 배경색을 변수로 처리한다.

이렇게 짜두면 디자이너가 차트 색상 바꿔달라고 할 때 TSX 파일을 건드릴 필요가 없다. CSS만 고치면 끝난다.

:root {
  --bg-chart-card: #ffffff;
  --text-chart-main: #172033;
  --border-chart-grid: #e2e8f0;
  --ts-chart-1: #2563eb;
  --ts-chart-2: #f97316;
}

[data-theme='dark'] {
  --bg-chart-card: #0f172a;
  --text-chart-main: #f8fafc;
  --border-chart-grid: #334155;
  --ts-chart-1: #60a5fa;
  --ts-chart-2: #fb923c;
}

.custom-tooltip {
  background-color: var(--bg-chart-card);
  color: var(--text-chart-main);
  border: 1px solid var(--border-chart-grid);
  padding: 8px 12px;
  border-radius: 6px;
}

데이터 10만 개 던져도 안 뻗게 만들기

포인트 10만 개를 SVG로 렌더링하면 DOM 노드가 10만 개 생성된다. 브라우저 스레드가 버틸 수가 없다. 멈칫거리는 게 눈에 보인다.

해결책은 두 가지를 섞는 거다. Canvas 렌더러로 전환해서 DOM 생성 비용을 없애고, 화면 픽셀 너비보다 의미 없이 빽빽한 데이터는 LTTB(Largest Triangle Three Buckets) 알고리즘으로 압축한다. LTTB는 데이터의 뾰족한 특징(최댓값, 최솟값)을 살리면서 점의 개수를 줄여준다.

export function lttbDownsample<T>(
  data: T[],
  xAccessor: (d: T) => number,
  yAccessor: (d: T) => number,
  threshold: number
): T[] {
  const dataLength = data.length;
  if (threshold >= dataLength || threshold === 0) return data;

  const sampled: T[] = [];
  let sampledIndex = 0;
  const bucketSize = (dataLength - 2) / (threshold - 2);

  sampled[sampledIndex++] = data[0];

  for (let i = 0; i < threshold - 2; i++) {
    let avgX = 0;
    let avgY = 0;
    const avgRangeStart = Math.floor((i + 1) * bucketSize) + 1;
    const avgRangeEnd = Math.floor((i + 2) * bucketSize) + 1;
    const currentAvgRangeEnd = avgRangeEnd < dataLength ? avgRangeEnd : dataLength;

    for (let j = avgRangeStart; j < currentAvgRangeEnd; j++) {
      avgX += xAccessor(data[j]);
      avgY += yAccessor(data[j]);
    }
    avgX /= (currentAvgRangeEnd - avgRangeStart);
    avgY /= (currentAvgRangeEnd - avgRangeStart);

    const rangeOffs = Math.floor((i + 0) * bucketSize) + 1;
    const rangeTo = Math.floor((i + 1) * bucketSize) + 1;
    const pointAX = xAccessor(sampled[sampledIndex - 1]);
    const pointAY = yAccessor(sampled[sampledIndex - 1]);

    let maxArea = -1;
    let maxAreaPointIndex = rangeOffs;

    for (let j = rangeOffs; j < rangeTo; j++) {
      const area = Math.abs(
        (pointAX - avgX) * (yAccessor(data[j]) - pointAY) -
        (pointAX - xAccessor(data[j])) * (avgY - pointAY)
      ) * 0.5;

      if (area > maxArea) {
        maxArea = area;
        maxAreaPointIndex = j;
      }
    }
    sampled[sampledIndex++] = data[maxAreaPointIndex];
  }

  sampled[sampledIndex++] = data[dataLength - 1];
  return sampled;
}

대용량 시계열을 다룰 때는 LTTB로 1000개 수준까지 줄여서 넘기자. 그리고 spatialIndex: true 옵션을 켜서 마우스 포인터 감지 복잡도를 낮춘다. 마지막으로 @tanstack/charts/react/canvas 패키지의 Canvas 렌더러를 붙이면 60fps가 뽑힌다.

Cursor나 Copilot한테 차트 코드 맡기는 법

Recharts처럼 JSX 태그가 깊게 중첩된 형태는 AI 어시스턴트가 환각을 잘 일으킨다. 이상한 속성을 만들어내거나 열린 태그를 닫지 않는다.

TanStack Charts처럼 객체 선언식으로 작성되는 라이브러리는 타입 스키마만 잘 쥐여주면 AI가 코드를 깔끔하게 뽑아낸다. 프로젝트에 아래 같은 스키마 타입을 하나 정의해두고 AI 프롬프트나 규칙 파일(.cursorrules)에 넣어두자.

export type ScaleType = 'linear' | 'band' | 'utc' | 'point';
export type MarkType = 'lineY' | 'barY' | 'dot' | 'areaY';

export interface AIScaleConfig {
  type: ScaleType;
  nice?: boolean;
  grid?: boolean;
  padding?: number;
}

export interface AIMarkConfig {
  type: MarkType;
  xKey: string;
  yKey: string;
  color?: string;
  strokeWidth?: number;
}

export interface AIChartGenerationSchema {
  data: Record<string, unknown>[];
  scales: {
    x: AIScaleConfig;
    y: AIScaleConfig;
  };
  marks: AIMarkConfig[];
  tooltip?: {
    enabled: boolean;
    className?: string;
  };
}

@tanstack/charts/scales/linear처럼 정확한 서브패스에서 임포트하라는 규칙과 useMemo 사용 규칙까지 프롬프트에 써두면 AI가 뱉은 코드를 수정 없이 바로 쓸 수 있다.