생성일: 2026-05-17
분석 도구: 정적 분석 (Big-O / 메모리 / React deps / 에러 바운더리)
대상 파일: DiggingMap.tsx · page.tsx · match/route.ts · generate/route.ts · ContentTipsPanel.tsx
| 심각도 | 건수 | 주요 영역 |
|---|---|---|
| 🔴 HIGH | 3 | D3 tick 내 setState, 드래그 이벤트 누수, window 리스너 미해제 |
| 🟡 MEDIUM | 4 | O(n²) 노드 탐색, sort() 셔플 편향, 내부 setTimeout 미추적, Anthropic 타임아웃 없음 |
| 🟢 LOW | 3 | brandFeatures find() 중첩, 마커 배열 선언 오류(이미 수정됨), 에러 바운더리 부재 |
파일: src/components/pulse/DiggingMap.tsx
라인: sim.on('tick', () => { ... setHoveredLink(...) })
심각도: 🔴 HIGH — 성능
sim.on('tick', () => {
linkSel.attr('x1', ...).attr('y1', ...).attr('x2', ...).attr('y2', ...);
nodeSel.attr('transform', ...);
// 🚨 60fps마다 React setState 호출 → 매 프레임 리렌더 유발
setHoveredLink((prev) => {
if (!prev) return null;
const l = links.find((l) => l.id === prev.id); // 🚨 O(n) in every frame
if (!l) return null;
const s = l.source as GNode; const t = l.target as GNode;
return { id: prev.id, x: ((s.x ?? 0) + (t.x ?? 0)) / 2, y: ((s.y ?? 0) + (t.y ?? 0)) / 2 };
});
});
// hoveredLinkRef로 DOM 좌표를 추적 — React 리렌더 없이 SVG 오버레이 갱신
const hoveredLinkRef = useRef<LinkMidpoint | null>(null);
sim.on('tick', () => {
linkSel.attr('x1', (d) => (d.source as GNode).x ?? 0)
.attr('y1', (d) => (d.source as GNode).y ?? 0)
.attr('x2', (d) => (d.target as GNode).x ?? 0)
.attr('y2', (d) => (d.target as GNode).y ?? 0);
nodeSel.attr('transform', (d) => `translate(${d.x ?? 0},${d.y ?? 0})`);
// ✅ setState 제거 — hoveredLink 위치는 mouseenter/mouseleave에서만 갱신
// tick에서는 ref에만 좌표 기록
if (hoveredLinkRef.current) {
const l = linksMap.get(hoveredLinkRef.current.id); // O(1) Map 조회
if (l) {
const s = l.source as GNode; const t = l.target as GNode;
hoveredLinkRef.current = {
id: hoveredLinkRef.current.id,
x: ((s.x ?? 0) + (t.x ?? 0)) / 2,
y: ((s.y ?? 0) + (t.y ?? 0)) / 2,
};
}
}
});
setHoveredLink가 D3 tick(≈60fps)마다 호출 → 초당 60회 React 리렌더