Files
2nd/10_Wiki/Topics/Frontend/대규모 데이터 렌더링 및 가상화 최적화.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

187 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: wiki-2026-0508-대규모-데이터-렌더링-및-가상화-최적화
title: 대규모 데이터 렌더링 및 가상화 최적화
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Virtualization, Windowing, Virtual List, 가상 스크롤]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [frontend, virtualization, performance, react, rendering]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: react
---
# 대규모 데이터 렌더링 및 가상화 최적화
## 매 한 줄
> **"매 보이는 것만 그린다"**. 100k row를 한 번에 mount 하면 DOM이 죽는다. Windowing 으로 viewport 안 ~30개만 render → scroll 시 recycle. 2026 표준은 TanStack Virtual / react-virtuoso, content-visibility CSS, 그리고 React 19의 `<Activity>` (offscreen).
## 매 핵심
### 매 왜 가상화가 필수인가
- DOM node 1개 ≈ 1-5KB memory + layout/paint cost.
- 10k+ row mount → 수백 MB heap, scroll jank, INP > 500ms.
- React reconciliation도 O(n) — fiber tree가 거대해지면 commit이 느려진다.
### 매 windowing 전략
- **Fixed size**: row height 동일 → offset = index × height. 가장 빠름.
- **Variable size**: row 마다 다름 → measured cache + estimated. ResizeObserver 로 동적 측정.
- **Dynamic load**: scroll 끝 도달 시 fetch (infinite scroll) — virtualization 과 결합.
### 매 응용
1. Admin table 100k+ row.
2. Chat history (역방향 virtualization).
3. Image gallery / Pinterest grid.
4. Code editor (Monaco-style line virtualization).
## 💻 패턴
### TanStack Virtual (2026 표준)
```tsx
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }: { items: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48,
overscan: 5,
});
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(v => (
<div
key={v.key}
data-index={v.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${v.start}px)`,
}}
>
{items[v.index].name}
</div>
))}
</div>
</div>
);
}
```
### Variable height with measurement
```tsx
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // 매 best guess
measureElement: el => el.getBoundingClientRect().height, // 매 actual measure
overscan: 8,
});
```
### CSS content-visibility (zero-JS virtualization)
```css
.row {
content-visibility: auto;
contain-intrinsic-size: 0 48px; /* 매 placeholder size */
}
```
브라우저가 viewport 밖 element를 자동으로 skip layout/paint. React 없이도 효과.
### Infinite scroll + virtualization
```tsx
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['rows'],
queryFn: ({ pageParam = 0 }) => fetchPage(pageParam),
getNextPageParam: last => last.nextCursor,
});
const allRows = data?.pages.flatMap(p => p.items) ?? [];
const virtualizer = useVirtualizer({ count: allRows.length, /* ... */ });
useEffect(() => {
const last = virtualizer.getVirtualItems().at(-1);
if (last && last.index >= allRows.length - 5 && hasNextPage) {
fetchNextPage();
}
}, [virtualizer.getVirtualItems()]);
```
### Grid virtualization (2D)
```tsx
const rowVirtualizer = useVirtualizer({ count: rows, /* ... */ });
const colVirtualizer = useVirtualizer({
horizontal: true,
count: cols,
getScrollElement: () => parentRef.current,
estimateSize: () => 120,
});
// 매 nested loop: rows × cols 의 visible cells only.
```
### React 19 `<Activity>` for tab-like UI
```tsx
<Activity mode={isActive ? 'visible' : 'hidden'}>
<ExpensiveTab /> {/* 매 hidden 시 unmount X, but no render cost */}
</Activity>
```
### Server-side pagination + cursor
```ts
// 매 절대 client 에 100k row 보내지 마라.
GET /api/rows?cursor=abc123&limit=50
{ items: [...], nextCursor: 'def456' }
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| < 100 rows | 매 plain map. Virtualization X. |
| 100-1000 rows, fixed height | content-visibility CSS. |
| 1000+ rows | TanStack Virtual / react-virtuoso. |
| Variable height | measure + estimateSize. |
| Infinite stream | virtualization + cursor pagination. |
| Tab/accordion | React 19 `<Activity>`. |
**기본값**: TanStack Virtual + React Query infinite + content-visibility 보조.
## 🔗 Graph
- 부모: [[Frontend Performance]] · [[Rendering]]
- 변형: [[Infinite Scroll]] · [[Pagination]]
- 응용: [[Code Editor]]
- Adjacent: [[React Query]] · [[ResizeObserver]]
## 🤖 LLM 활용
**언제**: row count > 500, scroll 성능 issue, INP regression.
**언제 X**: static page, 매 작은 list (< 100), SEO-critical content (crawler 가 못 봄).
## ❌ 안티패턴
- **Mount all then hide**: `display: none` 으로 숨겨도 DOM 비용은 낸다.
- **Key as index**: row reorder 시 reconciliation 깨짐 — stable id 사용.
- **Inline measure on every render**: ResizeObserver + cache 필수.
- **No overscan**: scroll 시 빈 영역 보임. overscan 5-10.
- **Animation on virtualized rows**: 매 recycle 시 animation 초기화 — layout id 따로 관리.
## 🧪 검증 / 중복
- Verified (TanStack Virtual docs, Chrome DevTools, web.dev/content-visibility).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — virtualization + content-visibility + Activity 통합 |