큐레이션 요약
60fps의 React: Figma
Figma는 캔버스에서 댓글 핀이 이동할 때 발생하던 불필요한 React 렌더링을 줄여 스크롤 성능을 약 3배 개선했다. 댓글 수와 무관하게 편집기를 60fps에 가깝게 동작시키는 것이 목표였으며, Chrome Performance 도구와 React Profiler로 병목이 JavaScript 실행과 컴포넌트 재렌더링에 있음을 확인했다. 핵심 해결책은 뷰포트 변화에 실제로 영향을 받는 댓글 컴포넌트만 업데이트하고, 댓글 위치 계산과 변환 처리를 최적화하는 것이었다.
60fps를 목표로 한 댓글 스크롤
- Figma의 댓글은 캔버스 위 특정 위치에 고정된 “댓글 핀”으로 표시된다.
- 사용자가 캔버스를 이동하거나 확대·축소하면 댓글 핀도 뷰포트에 맞춰 계속 위치를 다시 계산해야 한다.
- 15fps나 30fps보다 60fps가 훨씬 부드러운 사용자 경험을 제공하므로, 댓글과 스레드가 많아져도 일정한 성능을 유지하는 것이 목표였다.
- 댓글 사용량이 증가하면서 대규모 팀과 파일에서 캔버스 반응성이 저하되기 시작했다.
WebGL 캔버스와 React 댓글 UI의 구조
- Figma 편집기는 WebGL과 WebAssembly를 사용하는 “브라우저 안의 브라우저”에 가까운 구조다.
- 일부 사용자 인터페이스는 TypeScript와 React로 구현되어 있지만, 일반적인 정적 React 화면과 달리 댓글은 캔버스의 이동과 확대·축소에 따라 동적으로 움직인다.
- 편집기의 뷰포트 정보는 Redux에 저장된다.
- 댓글 핀 컴포넌트는 Redux에서 뷰포트 정보를 가져와 캔버스 좌표를 화면에 표시할 위치로 변환한다.
- 뷰포트가 변경될 때마다 React 컴포넌트 트리 일부가 업데이트되므로, 업데이트 범위가 성능에 직접적인 영향을 준다.
성능 분석에서 발견한 병목
- Chrome Performance 도구에서 대부분의 프레임 시간이 렌더링이나 페인팅이 아니라 JavaScript 실행에 사용되는 것으로 나타났다.
- 댓글 30개인 화면에서 프레임당 약 68ms가 JavaScript에 소비되었고, 실제 화면은 약 19fps로 렌더링됐다.
- React Profiler에서는 댓글 화면 자체의 렌더링에는 약 1.8ms만 사용되고 있었다.
- 대신 뷰포트 변화와 직접 관련 없는 다음 컴포넌트들이 함께 재렌더링됐다.
- 왼쪽 패널
- 툴바
- 속성 패널
- 기타 고정 위치 UI
- 즉, 댓글 내용 렌더링보다 “변화가 없는 컴포넌트까지 다시 렌더링하는 것”이 더 큰 비효율이었다.
불필요한 재렌더링 줄이기
- 뷰포트 업데이트는 댓글 핀의 위치에는 필요하지만, 화면에 고정된 패널이나 툴바에는 필요하지 않다.
- 따라서 뷰포트 상태를 사용하는 컴포넌트의 범위를 댓글 영역으로 제한해야 한다.
- React 애플리케이션이 커질수록 상위 컴포넌트의 상태 변화가 하위 전체로 전파되면서 불필요한 렌더링이 발생하기 쉽다.
- React Profiler로 실제로 다시 렌더링되는 컴포넌트를 확인하면, 직관만으로 찾기 어려운 병목을 구체적으로 식별할 수 있다.
- 성능 개선은 댓글 컴포넌트 자체를 빠르게 만드는 것뿐 아니라, 댓글과 무관한 컴포넌트가 업데이트되지 않도록 컴포넌트 구조와 상태 구독 방식을 조정하는 데서 시작됐다.
댓글 핀 위치 변환 최적화
- 댓글 핀은 뷰포트가 바뀔 때마다 캔버스 좌표를 화면 좌표로 변환해야 한다.
- 이 변환 과정이 매 업데이트마다 React 렌더링과 결합되면 JavaScript 실행 비용이 커질 수 있다.
- Figma는 불필요한 컴포넌트 업데이트를 제거한 뒤 댓글 핀의 변환 처리도 최적화해, 캔버스 이동 중 위치 계산과 화면 반영 비용을 줄였다.
- 결과적으로 댓글 스크롤 FPS가 기존보다 약 3배 향상됐다.
실용적인 결론
- React 성능 문제에서는 먼저 “컴포넌트 하나의 렌더링 속도”보다 “불필요하게 다시 렌더링되는 컴포넌트가 무엇인지”를 확인하는 것이 효과적이다.
- Chrome Performance 도구로 프레임별 JavaScript 비용을 확인하고, React Profiler로 재렌더링 범위를 분석하는 조합이 유용하다.
- 자주 변하는 상태는 실제로 그 상태가 필요한 컴포넌트 가까이에 두고, 고정 UI가 동적 상태 변화에 구독되지 않도록 설계하는 것이 좋다.
관련 글
큐레이션 요약을 이어서 읽어보세요.