리액트 성능 최적화 필수 팁 5가지: 렌더링 지옥 탈출 실전 가이드

20251208_030425_리액트_성능_최적화_필수_팁_5가지:_불필요한_렌더링_줄이고_속도_높이는_방법

⚡ 10초 핵심 요약

  • 상태 배치 최적화: 상태를 컴포넌트 트리 하단으로 배치해 리렌더링 범위 축소
  • 리스트 가상화: 수천 개의 데이터 중 화면에 보이는 것만 렌더링
  • 코드 스플리팅: 번들 사이즈를 쪼개 초기 로딩 속도(LCP) 극대화

버튼 하나 눌렀는데 1초 딜레이, 아직도 겪으시나요?

2025년이 끝나가는 지금, 사용자들의 눈높이는 그 어느 때보다 높아졌습니다. 웹앱이 네이티브 앱처럼 빠릿빠릿하게 반응하지 않으면 사용자는 3초도 기다려주지 않고 뒤로 가기를 누르죠. 하드웨어 성능은 좋아졌지만, 정작 우리의 코드는 불필요한 렌더링으로 CPU를 괴롭히고 있을지도 모릅니다.

특히 리액트(React)는 데이터가 변하면 UI를 다시 그리는 구조라, 자칫하면 화면 전체가 깜빡이는 ‘렌더링 지옥’에 빠지기 쉽습니다. 복잡한 이론은 접어두고, 당장 실무에 적용해 체감 속도를 2배 이상 올릴 수 있는 핵심 기법 5가지를 정리했습니다.

1. 상태(State) 위치만 바꿔도 절반은 성공

성능 최적화라고 하면 다들 useMemo부터 떠올리는데, 사실 가장 효과적인 건 ‘상태 밀어내기(State Colocation)’입니다. 부모 컴포넌트가 상태를 가지고 있으면, 그 상태가 변할 때 모든 자식 컴포넌트가 덩달아 리렌더링 됩니다.

예를 들어, 모달 창의 열림/닫힘 상태를 전체 페이지 컴포넌트가 가지고 있을 필요가 있을까요? 모달 컴포넌트 내부나 그 직계 부모로 상태를 옮기세요. 이것만으로도 불필요한 렌더링 트리의 연쇄 반응을 끊을 수 있습니다.

2. 리스트 가상화 (Windowing) 도입

데이터가 1,000개, 10,000개가 넘어가는 리스트를 그냥 map()으로 돌려서 렌더링하고 있다면, 브라우저는 비명부터 지릅니다. 사용자가 보는 화면(Viewport)은 한정적인데 보이지 않는 수천 개의 DOM 요소를 만드는 건 엄청난 낭비니까요.

이때 필요한 게 리스트 가상화(Virtualization)입니다. 스크롤 위치에 따라 현재 화면에 보이는 아이템만 렌더링하고, 스크롤을 내리면 위쪽 아이템은 메모리에서 지우고 아래쪽을 새로 그리는 방식이죠.

비교 항목 일반 렌더링 (map) 가상화 적용 (Virtualization)
초기 로딩 속도 매우 느림 (모든 아이템 생성) 매우 빠름 (보이는 것만 생성)
메모리 점유율 아이템 수에 비례해 폭증 항상 일정 수준 유지
스크롤 성능 뚝뚝 끊김 (Jank 발생) 60fps 유지하며 매끄러움

3. 리액트 컴파일러(React Compiler) 활용하기

2024년 정식 도입 이후, 2025년 현재는 리액트 컴파일러가 표준이 되었습니다. 예전처럼 개발자가 수동으로 useMemouseCallback을 덕지덕지 바르던 시절은 지났죠. 컴파일러가 빌드 타임에 자동으로 의존성을 파악해 메모이제이션을 처리해 줍니다.

하지만 방심은 금물입니다. 컴파일러가 최적화하지 못하는 ‘De-opt’ 코드가 있는지 린트(Lint) 도구로 확인해야 합니다. 객체나 배열을 변형(Mutation)하는 코드를 작성하면 컴파일러가 최적화를 포기해 버리니, 불변성을 지키는 습관은 여전히 유효합니다.

4. 코드 스플리팅으로 초기 로딩 다이어트

사용자가 처음 접속했을 때, 관리자 페이지나 마이페이지의 코드까지 전부 다운로드하게 할 필요는 없습니다. 동적 임포트(Dynamic Import)React.lazy를 활용해 코드를 쪼개세요.

라우트(Route) 단위로 코드를 분할하는 것이 가장 쉽고 효과적입니다. 초기 번들 사이즈(JS 파일 크기)가 줄어들면, 구글이 중요하게 여기는 LCP(Largest Contentful Paint) 점수가 극적으로 개선됩니다. 모바일 환경에서는 이 차이가 로딩 1초냐 5초냐를 가릅니다.

5. 렌더링 시점을 제어하는 디바운스(Debounce)와 스로틀(Throttle)

검색창에 타이핑할 때마다 API를 호출하고 컴포넌트를 다시 그린다면? 서버도 힘들고 클라이언트도 버벅거립니다. 사용자의 입력이 끝난 후 일정 시간이 지났을 때 한 번만 상태를 업데이트하는 디바운스(Debounce) 기법을 적용하세요.

  • 디바운스(Debounce): 검색어 입력, 윈도우 리사이징 종료 시점 처리에 적합
  • 스로틀(Throttle): 무한 스크롤 등 일정 간격으로 이벤트가 실행되어야 할 때 적합

자주 묻는 질문 (FAQ)

Q. 리액트 컴파일러가 나왔는데, 이제 useMemo는 아예 안 써도 되나요?

대부분의 경우 컴파일러가 자동으로 처리하지만, 매우 복잡한 계산 로직이나 특정 외부 라이브러리와의 호환성을 위해서는 여전히 수동 최적화가 필요할 때가 있습니다. 프로파일러로 측정 후 적용하세요.

Q. 최적화를 언제 시작하는 게 좋을까요?

‘조기 최적화는 만악의 근원’이라는 말이 있죠. 기능 구현이 먼저입니다. 하지만 렌더링이 눈에 띄게 느려지거나, 대규모 데이터를 다루는 리스트 컴포넌트라면 설계 단계부터 가상화를 고려해야 합니다.

마치며: 측정 없는 최적화는 추측일 뿐

오늘 소개한 5가지 팁만 적용해도 여러분의 리액트 앱은 훨씬 가벼워질 겁니다. 하지만 가장 중요한 건 ‘React DevTools’의 프로파일러(Profiler)를 켜는 습관입니다. 어디서 렌더링이 튀는지 눈으로 확인하고, 가장 병목이 심한 곳부터 하나씩 해결해 나가세요. 지금 당장 불필요한 console.log를 지우는 것부터 시작해 보는 건 어떨까요?

하나만 더 볼까?