| name | vercel-best-practice |
| description | Vercel Engineering의 React/Next.js 성능 최적화 가이드라인을 적용하여 렌더링 성능, 번들 크기, API 호출을 최적화합니다. |
| disable-model-invocation | false |
Vercel React Best Practices Skill
Vercel Engineering의 React/Next.js 성능 최적화 가이드라인을 Edwards 프로젝트에 적용합니다.
핵심 원칙
- 불필요한 리렌더링 방지
- 번들 크기 최소화
- API 호출 최소화
- 렌더링 성능 최적화
적용 규칙
Priority 5: Re-render Optimization (MEDIUM)
rerender-memo
React.memo를 사용하여 불필요한 리렌더링 방지
const InlineEditableRow = memo<Props>(({ project, onUpdate }) => {
return <tr>...</tr>;
});
export function InlineEditableRow({ project, onUpdate }: Props) {
return <tr>...</tr>;
}
적용 대상:
- 테이블 행 컴포넌트
- 셀 컴포넌트
- 리스트 아이템 컴포넌트
rerender-functional-setstate
setState에서 변경 없으면 스킵
setColumnWidths(prev => {
if (prev[column] === newWidth) return prev;
return { ...prev, [column]: newWidth };
});
setColumnWidths({ ...columnWidths, [column]: newWidth });
rerender-dependencies
useEffect 의존성을 primitive 값으로 최적화
const filteredProductLines = useMemo(() => {
return selectedBusinessUnitId
? productLines.filter(pl => pl.business_unit_id === selectedBusinessUnitId)
: productLines;
}, [productLines, selectedBusinessUnitId]);
const valueExistsInFiltered = useMemo(() => {
return value ? filteredProductLines.some(pl => pl.id === value) : true;
}, [value, filteredProductLines]);
useEffect(() => {
if (value && !valueExistsInFiltered) {
onChange('');
}
}, [value, valueExistsInFiltered, onChange]);
Priority 6: Rendering Performance (MEDIUM)
rendering-hoist-jsx
정적 JSX를 컴포넌트 외부로 호이스팅
const SortIconDefault = <ArrowUpDown className="h-3 w-3 ml-1 text-gray-400" />;
const SortIconAsc = <ArrowUp className="h-3 w-3 ml-1 text-gray-700" />;
const SortIconDesc = <ArrowDown className="h-3 w-3 ml-1 text-gray-700" />;
export function ProjectInlineTable() {
return (
<th>
{sortField === 'name' && sortDirection === 'asc' ? SortIconAsc : SortIconDefault}
</th>
);
}
export function ProjectInlineTable() {
return (
<th>
{sortField === 'name' && sortDirection === 'asc'
? <ArrowUp className="h-3 w-3 ml-1 text-gray-700" />
: <ArrowUpDown className="h-3 w-3 ml-1 text-gray-400" />
}
</th>
);
}
적용 대상:
- 아이콘 컴포넌트
- Placeholder 텍스트
- 정적 버튼/링크
Priority 7: JavaScript Performance (LOW-MEDIUM)
js-index-maps
배열.find() 대신 Map을 사용하여 O(1) 조회
const FUNDING_ENTITY_MAP = new Map(
FUNDING_ENTITY_OPTIONS.map(o => [o.value, o.label])
);
{FUNDING_ENTITY_MAP.get(project.funding_entity_id ?? '') || EmptyPlaceholder}
const option = FUNDING_ENTITY_OPTIONS.find(o => o.value === project.funding_entity_id);
{option?.label || '-'}
js-combine-iterations
여러 순회를 하나로 통합
const sortedProjects = useMemo(() => {
let result: Project[] = [];
for (let i = 0; i < projects.length; i++) {
const p = projects[i];
if (hasCategories && (!p.category || !selectedCategories.includes(p.category))) continue;
if (hasStatuses && !selectedStatuses.includes(p.status)) continue;
result.push(p);
}
if (sortField && sortDirection) {
result.sort((a, b) => { ... });
}
return result;
}, [projects, selectedCategories, selectedStatuses, sortField, sortDirection]);
const filtered = projects.filter(p => selectedCategories.includes(p.category));
const sorted = filtered.sort((a, b) => { ... });
TanStack Query 최적화
전역 설정 (main.tsx)
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
refetchOnWindowFocus: false,
retry: 1,
retryDelay: 1000,
},
},
});
참조 데이터 장기 캐싱
const { data: jobPositions } = useQuery({
queryKey: ['jobPositions'],
queryFn: () => apiClient.get('/job-positions').then(r => r.data),
staleTime: 30 * 60 * 1000,
});
CSS 최적화
content-visibility
tbody.virtualized {
content-visibility: auto;
contain-intrinsic-size: auto 300px;
}
적용 대상: 대량 데이터 테이블 (500+ 행)
Route-Level Code Splitting
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const ReportsPage = lazy(() => import('./pages/ReportsPage'));
<Route path="/" element={
<Suspense fallback={<PageLoader />}>
<DashboardPage />
</Suspense>
} />
효과: 초기 번들 크기 61% 감소 (1,127KB → 437KB)
적용 체크리스트
컴포넌트 최적화
상태 관리 최적화
데이터 처리 최적화
번들 최적화
성능 측정
빌드 결과
✓ built in 4.10s
ProjectsPage: 41.37 kB → 30.94 kB (-25% 감소)
번들 크기 목표
- 메인 번들: < 500KB (gzipped)
- 페이지별 청크: < 100KB
- 총 번들: < 1MB (gzipped)
관련 문서
workthrough/2026-01-28_14_30_vercel-react-best-practices.md - 초기 적용
workthrough/2026-01-31_19_30_vercel-best-practices-optimization.md - 인라인 테이블 최적화
workthrough/2026-01-28_11_00_route-level-code-splitting.md - Code splitting
참고 자료