- React 빌드를 올바르게 배포하고 번들러를 최적화하는 것(프로덕션 및 프로파일링 버전)은 모든 본격적인 성능 개선 작업의 기본입니다.
- React DevTools와 브라우저 성능 추적을 이용한 프로파일링을 통해 불필요한 렌더링, 느린 효과, 서버 병목 현상을 파악하고 이를 개선할 수 있습니다.
- 메모이제이션, 불변성 및 가상화는 함께 작동하여 렌더링 빈도를 줄이고, 렌더링당 작업량을 줄이며, 대규모 UI를 부드럽게 유지합니다.
- 코드 분할, SSR, 웹 워커 및 지속적인 모니터링을 통해 빠른 초기 로딩, 반응성 있는 상호 작용 및 확장 가능한 성능을 보장합니다.

React는 기본적으로 매우 빠른 속도를 제공하지만, 앱 규모가 커짐에 따라 미묘한 성능 저하 문제가 쉽게 누적될 수 있습니다. 매끄러운 인터페이스를 느리고 배터리 소모가 심한 괴물로 바꿔놓는 요소들이 있습니다. 긴 목록, 무거운 컴포넌트, 어색한 상태 구조, 그리고 프로덕션 환경에서의 디버그 빌드는 모두 사용자가 페이지를 이탈하게 만드는 원인이 됩니다.
다행인 점은 React에 렌더링 성능을 측정하고 이해하고 개선하는 데 사용할 수 있는 풍부한 도구 모음이 포함되어 있다는 것입니다.그리고 그 주변의 생태계(번들러, 프로파일러, 윈도우 라이브러리, 웹 워커, SSR 프레임워크)는 규모가 커져도 UI를 빠르게 유지하는 데 필요한 모든 것을 제공합니다. 이 가이드에서는 이러한 도구들을 심층적으로 살펴보고, 서로 어떻게 연동되는지 보여주며, 팀에서 종종 간과하지만 반드시 알아두면 유용한 몇 가지 팁을 소개합니다.
React 빌드 환경에 맞는 빌드를 사용하세요: 개발, 프로덕션, 프로파일링.
React 앱의 첫 번째 성능 점검은 개발 버전이 아닌 실제 운영 버전을 배포하고 있는지 확인하는 것입니다.개발 빌드에는 코딩 중에는 매우 유용하지만 프로덕션 환경에서는 눈에 띄게 속도가 느려지고 용량이 커지는 수많은 친절한 경고, 추가 검사 및 디버깅 도우미가 포함되어 있습니다.
React 개발자 도구 브라우저 확장 프로그램을 사용하면 사용 중인 빌드를 확인할 수 있습니다.React를 사용하는 사이트를 열면 확장 프로그램 아이콘의 배경이 프로덕션 환경에서는 어두운 색으로, 개발 환경에서는 빨간색으로 표시됩니다. 만약 실제 운영 중인 사이트에서 빨간색 아이콘이 보인다면, 번들러 설정에서 잘못된 빌드가 유출되고 있는 것입니다.
Create React App으로 부트스트랩한 프로젝트의 경우, 최적화된 프로덕션 번들을 생성하는 것은 빌드 스크립트를 실행하는 것만으로 간단합니다.이는 축소된 번들을 다음 위치에 출력합니다. build/ 디렉토리입니다. 로컬 개발 중에는 다음을 준수해야 합니다. npm start (또는 그와 동등한) 버전을 사용하고, 배포 또는 실제 성능 벤치마크를 위해서만 프로덕션 빌드를 실행합니다.
React 및 React DOM의 UMD 단일 파일 빌드에 의존하는 경우(예: 번들링되지 않은 환경)확장자가 로 끝나는 파일을 모두 포함했는지 확인하십시오. .production.min.js축소되지 않았거나 프로덕션용이 아닌 파일은 개발 목적으로만 사용해야 하며, 사용자에게 불필요한 디버깅 부담을 안겨줄 수 있습니다.
번들러: Browserify, Rollup, Brunch 및 webpack
각 번들러마다 React의 프로덕션 최적화를 완벽하게 활성화하기 위해 필요한 설정 변경 사항이 다릅니다.하지만 이 모든 방법은 기본적으로 동일한 아이디어를 따릅니다. 즉, 환경을 프로덕션으로 설정하고, 개발 전용 브랜치를 제거하고, 결과 JavaScript를 최소화하는 것입니다.
Brunch를 사용할 경우, 권장되는 방법은 미니파이어 플러그인을 설치하는 것입니다. terser-brunch그런 다음 프로덕션 플래그를 사용하여 빌드를 실행하세요(예: ). -p이 설정을 통해 개발 단계의 경고가 제거되고 최종 번들이 최대한 압축됩니다.
Browserify에서는 일반적으로 몇 가지 변환을 특정 순서대로 연결합니다.: 먼저 신청하세요 envify 전 세계적으로 주입하기 NODE_ENV="production", 그런 다음 적용하세요 uglifyify 전역적으로 개발 관련 가져오기 및 코드 경로를 삭제하고 마지막으로 번들을 파이프라인을 통해 전달합니다. terser 코드 변형 및 압축을 위한 단계입니다. 각 단계는 다음 변환을 위한 코드 준비 단계이므로 순서가 중요합니다.
Rollup을 사용할 때는 세 가지 플러그인을 연결하여 간결한 프로덕션 빌드를 구현할 수 있습니다.: replace 환경을 프로덕션 모드로 설정합니다. commonjs CommonJS 모듈을 번들링할 수 있도록 허용합니다. terser 최종 축소 및 변형 작업을 수행합니다. 이 조합을 통해 개발 전용 도우미 파일 없이 작고 바로 사용 가능한 패키지가 생성됩니다.
웹팩 4 이상 버전에서는 프로덕션 모드를 활성화하면 파일 축소를 포함한 여러 최적화 기능이 자동으로 작동합니다.. 환경 mode: 'production' Terser 내부에서 와이어링을 수행하고 React의 프로덕션 동작을 활성화합니다. NODE_ENV 일치하는 부분이 있습니다. 특별한 요구 사항이 없는 한 별도의 축소 도구를 추가할 필요는 없습니다.
React 빌드 프로파일링
React는 일반 개발 및 프로덕션 빌드 외에도 성능 분석에 초점을 맞춘 특별한 프로파일링 빌드를 제공합니다.이 변형은 React 내부적으로 계측 기능을 제공하여 DevTools Profiler와 같은 도구가 매우 상세한 타이밍 정보를 수집할 수 있도록 합니다.
브라우저 환경에서 프로파일링 빌드를 사용하려면 가져오기를 수행해야 합니다. react-dom/profiling 대신 react-dom/client 일반적으로 번들러 별칭을 구성하여 모든 임포트를 수동으로 수정할 필요가 없도록 합니다. 일부 프레임워크는 이미 이러한 동작을 전환할 수 있는 플래그 또는 모드를 제공합니다.
React의 이전 버전(17 이전)은 표준 사용자 타이밍 API에 의존했습니다. 브라우저의 성능 패널에 표시되는 마크와 측정값을 생성합니다. 최신 React는 이러한 기능을 React 개발자 도구의 전용 프로파일러 탭과 결합하여 구성 요소를 직접 자세히 살펴볼 수 있도록 합니다.
React 성능 이해 및 측정
측정하지 않으면 개선할 수 없으므로 React 성능 작업은 항상 프로파일링부터 시작해야 합니다.즉, 브라우저 도구와 React 전용 프로파일러를 사용하여 실제로 시간이 어디에 소요되는지, 어떤 컴포넌트가 필요 이상으로 자주 다시 렌더링되는지 확인하는 것입니다.
크롬 개발자 도구의 성능 패널은 브라우저의 작동 방식을 파악하는 데 있어 기본적인 기준이 됩니다.자바스크립트 실행, 네트워크 요청, 레이아웃, 화면 렌더링, 이벤트 루프 지연 및 사용자 지정 추적 등 모든 활동이 통합 타임라인에 표시됩니다. React는 프레임워크별 활동을 보여주는 특수 트랙을 통해 이 뷰에 통합됩니다.
최신 React는 스케줄러, 컴포넌트 및 서버 추적을 일반적인 브라우저 추적과 일치하도록 제공합니다.이를 통해 네트워크, JavaScript 및 React 업데이트를 동기화된 방식으로 볼 수 있으므로 부하가 걸릴 때만 발생하는 버벅거림이나 이상한 멈춤 현상을 추적할 때 매우 유용합니다.
스케줄러는 트랙 및 렌더링 단계를 관리합니다.
스케줄러는 다양한 우선순위에 따라 작업을 조율하는 React 내부 추상화 계층입니다.성능 추적에서는 차단 작업(대부분 동기식 사용자 주도 업데이트)과 전환 작업(백그라운드 UI 업데이트)에 대한 별도의 하위 트랙을 볼 수 있습니다. startTransition), 긴장감을 유발하는 작업 및 더 긴급한 일이 없을 때 실행되는 유휴 작업.
각 렌더링 패스는 타임라인에서 확인할 수 있는 여러 단계들을 거칩니다.: 업데이트 단계(렌더링을 트리거하는 요소), 렌더링 단계(React가 컴포넌트를 호출하고 다음 트리를 구성하는 단계), 커밋 단계(DOM이 변경되고 레이아웃 효과가 적용되는 단계) useLayoutEffect 실행) 및 나머지 효과 단계(수동 효과가 적용되는 단계) useEffect 일반적으로 페인트칠 후에 실행됩니다.
렌더링 중에 예약된 상태 변경인 연쇄 업데이트는 숨겨진 성능 문제의 전형적인 원인입니다.개발 과정에서 React는 타임라인에 이러한 사항을 표시하고 추가 업데이트가 예약된 구성 요소와 메서드까지 보여주어 의도치 않은 렌더링 루프나 반복 작업을 방지할 수 있도록 도와줍니다.
구성 요소 트랙: 렌더링 및 효과용 화염 그래프
컴포넌트 트랙은 각 컴포넌트(및 그 하위 컴포넌트)의 렌더링에 걸리는 시간을 시각화합니다. 플레임그래프를 사용합니다. 그래프에서 블록이 넓을수록 해당 컴포넌트 하위 트리가 해당 렌더링 패스에서 더 많은 시간을 소비합니다.
React는 효과 지속 시간을 별도의 플레임그래프로도 표시합니다. 스케줄러 트랙의 해당 단계와 동일한 색상 구성표를 사용하여 렌더링 시간과 효과 적용 시간을 한눈에 구분할 수 있습니다.
마운트, 언마운트, 재연결 및 연결 해제와 같은 추가 이벤트는 이러한 플레임그래프에 주석으로 표시됩니다.예를 들어, 나무에 새로운 부분을 설치하거나 기존 부분을 제거하는 경우 표시가 되며, 다음과 같은 몇 가지 특징도 표시됩니다. <Activity> 구성 요소에는 자체적인 재연결/연결 해제 표시기가 있습니다.
개발 환경에서 컴포넌트 트랙의 렌더링 항목을 클릭하면 어떤 속성이 변경되었는지 확인할 수 있습니다.이는 실제 값은 변경되지 않고 참조만 계속 바뀌는 불필요한 렌더링이나 소품을 찾아낼 때 매우 유용합니다.
서버 추적: 요청 및 서버 구성 요소
React 서버 컴포넌트를 사용하는 경우, 성능 분석 도구를 통해 서버 측 동작도 확인할 수 있습니다."서버 요청" 트랙은 궁극적으로 서버 구성 요소에 데이터를 제공하는 Promise를 집계하며, 여기에는 다음 호출이 포함됩니다. fetch 또는 비동기 파일 시스템 작업.
React는 타사 헬퍼에서 생성된 Promise들을 하나의 span으로 묶으려고 시도합니다. 그러면 다음과 같은 논리 연산 하나를 보게 될 것입니다. getUser 12개의 저수준보다는 fetch 호출. 스팬을 클릭하면 해당 스팬이 생성된 위치와, 가능한 경우 해결된 값 또는 거부 사유가 표시됩니다.
별도의 서버 구성 요소 트랙에서는 서버 구성 요소 트리와 대기 중인 Promise에 걸리는 시간을 표시합니다.또한 플레임그래프 형태로도 표현됩니다. React가 서버 컴포넌트를 동시에 렌더링할 수 있는 경우 기본 트랙과 추가 병렬 트랙을 생성합니다. 동시 실행 수가 특정 수를 초과하면 추가 작업이 그룹화되어 뷰의 가독성을 유지합니다.
불필요한 렌더링 줄이기: React.memo, useMemo, useCallback 및 PureComponent 사용
React 앱에서 가장 흔하고 큰 성능 저하 원인 중 하나는 불필요한 리렌더링입니다.부모 컴포넌트가 업데이트되면 자식 컴포넌트도 기본적으로 다시 렌더링됩니다. 입력값(props)이 동일하고 출력 DOM이 실제로 변경되지 않더라도 마찬가지입니다.
React는 이러한 불필요한 작업을 줄이는 데 도움이 되는 여러 도구를 제공합니다. React.memo 기능성 부품의 경우, React.PureComponent 클래스 구성 요소의 경우, useMemo/useCallback props로 전달되는 값을 안정화하기 위한 훅이러한 방법들이 모든 성능 문제를 마법처럼 해결해 주지는 않지만, 현명하게 사용하면 큰 차이를 만들어낼 수 있습니다.
React.memo 함수형 컴포넌트를 래핑하고, 해당 컴포넌트의 props가 이전 props와 얕은 수준으로 같을 경우 재렌더링을 건너뜁니다.이는 컴포넌트가 동일한 props로 자주 렌더링되거나, 렌더링 로직이 복잡하거나, 프로파일러를 통해 병목 현상이 발생한다는 증거가 있을 때 가장 유용합니다.
컴포넌트를 메모이제이션할 때는 해당 컴포넌트의 props가 불필요하게 식별자를 변경하지 않도록 해야 합니다.부모 JSX 내부에 매 렌더링 시마다 새 객체나 인라인 함수를 생성하면 얕은 비교가 무효화되어 논리적 데이터가 동일하더라도 자식이 다시 렌더링됩니다.
여기는 useMemo useCallback 올: useMemo 다른 상태에서 파생된 객체 또는 배열 값을 안정화하여 종속성이 변경될 때만 변경되도록 합니다. useCallback 메모이제이션된 자식 요소에 전달된 콜백에 대해 안정적인 함수 참조를 제공합니다.
클래스 컴포넌트: shouldComponentUpdate 및 React.PureComponent
내부적으로 대부분의 React 렌더링 최적화는 결국 특정 조건을 제어할지 여부를 결정하는 것으로 귀결됩니다. shouldComponentUpdate 참 또는 거짓을 반환합니다.기본 구현은 항상 true를 반환하며, 이는 속성이나 상태가 변경될 때마다 해당 컴포넌트와 그 하위 트리에 대한 렌더링 및 조정이 트리거됨을 의미합니다.
재정의함으로써 shouldComponentUpdate업데이트가 필요 없는 서브트리에 대해서는 작업을 건너뛸 수 있습니다.만약 false를 반환하면 React는 해당 함수를 호출하지 않습니다. render() 해당 컴포넌트 또는 그 하위 컴포넌트에 대해서는 아무런 조치도 취하지 않으며, 트리의 해당 부분에 대한 새 가상 DOM 노드와 이전 가상 DOM 노드를 비교조차 하지 않습니다.
일부 노드가 false를 반환하는 작은 컴포넌트 트리를 생각해 보세요. shouldComponentUpdateReact는 해당 분기로의 탐색을 완전히 건너뛸 수 있지만, 메서드가 true를 반환하는 다른 노드는 완전히 처리됩니다. 결국 렌더링된 출력이 실제로 변경된 노드만 DOM 변경을 유발합니다.
맞춤 제작을 하기 때문에 shouldComponentUpdate 논리는 반복적이며, React는 출시됩니다. React.PureComponent이는 현재 및 이전 props와 state에 대한 얕은 비교를 구현합니다. 얕은 비교에서 변경 사항이 없으면 React는 해당 클래스 컴포넌트의 재렌더링을 안전하게 건너뛸 수 있습니다.
불변성과 얕은 비교가 실패할 수 있는 이유
얕은 비교는 값이 변경되면 해당 참조도 변경될 것이라고 가정합니다. 하지만 기존 배열이나 객체를 제자리에서 변경하는 순간 이 가정은 더 이상 유효하지 않게 됩니다.이는 불변성 기반 최적화와 가변 데이터 구조를 결합할 때 발생하는 전형적인 버그의 원인입니다.
상상 ListOfWords 수신하는 구성 요소 words 배열을 쉼표로 구분하여 렌더링합니다.부모와 짝을 이룬다 WordAdder 같은 배열에 새 단어를 추가하는 구성 요소입니다. 만약 ListOfWords 확장하다 PureComponent얕은 비교는 동일한 배열 참조를 확인하고 아무것도 변경되지 않았다고 가정하므로 UI가 업데이트되지 않습니다.
해결책은 props나 state를 직접 변경하는 대신 데이터가 변경될 때 새로운 배열이나 객체를 생성하는 것입니다.. 대신에 words.push(newWord), 당신은 사용할 것입니다 words.concat(newWord) 또는 스프레드 구문 [...words, newWord]이는 배열에 대한 새로운 참조를 생성하고 올바른 업데이트를 트리거합니다.
동일한 원리가 사물에도 적용됩니다.: 재할당하는 것보다 colormap.right = 'blue' 기존 객체에 대해서는, 새 객체를 반환하려면 다음을 사용합니다. Object.assign({}, colormap, { right: 'blue' }) 또는 객체 스프레드 구문 { ...colormap, right: 'blue' }이는 피상적인 비교가 새로운 기준을 파악하고 변화를 인식하도록 보장합니다.
데이터가 복잡하게 중첩될 경우, 수동으로 불변성을 유지하는 것은 매우 어려워질 수 있습니다.Immer나 immutability-helper 같은 라이브러리를 사용하면 명령형처럼 보이면서도 내부적으로는 새로운 불변 구조를 생성하는 코드를 작성할 수 있어 다양한 환경에서 활용하기 좋습니다. PureComponent React.memo.
긴 목록과 복잡한 UI를 가상화합니다.
수백 또는 수천 개의 DOM 노드를 한 번에 렌더링하는 것은 React 성능을 급격히 저하시키는 가장 빠른 방법 중 하나입니다.특히 저사양 기기에서 또는 복잡한 레이아웃과 이미지를 함께 사용할 경우 더욱 그렇습니다. 효율적인 조정이 이루어지더라도 메모리와 화면에 그렇게 많은 노드를 보유하는 것 자체가 비용 부담이 큽니다.
윈도우 처리 또는 리스트 가상화는 뷰포트에 현재 보이는 리스트 부분만 렌더링함으로써 이 문제를 해결합니다.사용자가 스크롤하면 React는 뷰에 새로 추가되는 항목을 마운트하고 스크롤되어 사라지는 항목을 언마운트하여 렌더링되는 행 수를 대략 일정하게 유지합니다.
인기 있는 라이브러리들 react-window react-virtualized 목록, 그리드 및 테이블에 재사용 가능한 구성 요소를 제공합니다. 효율적인 가상화 전략을 구현합니다. 렌더링할 항목의 계산, 크기 조정, 컨테이너 스크롤링, 심지어 무한 로딩 동작까지 처리합니다.
가상화 설정은 일반적으로 세 단계로 이루어집니다.적절한 구성 요소를 선택하는 것(예: FixedSizeList 균일한 행의 경우 또는 VariableSizeList (동적 높이의 경우) 컨테이너에 고정된 높이를 부여합니다. overflow: scroll그리고 라이브러리가 요청하는 항목 구성 요소만 렌더링하며, 일반적으로 메모이제이션을 사용합니다. React.memo 불필요한 재렌더링을 방지하기 위해.
가상화 기술을 제대로 활용하면 대규모 데이터 세트에서도 스크롤 성능이 원활하고 메모리 사용량이 낮게 유지됩니다.실제 앱들은 이 기술을 활용하여 음악 리뷰, 전자상거래 카탈로그, 이메일 수신함 등 방대한 컬렉션을 사용자 인터페이스가 멈추지 않고 효율적으로 탐색해 왔습니다.
가상화된 목록의 경우 접근성에 대해 좀 더 세심한 주의를 기울여야 합니다.키보드 탐색이 제대로 작동하는지, 항목이 마운트 및 언마운트될 때 포커스가 올바르게 관리되는지, 그리고 화면 판독기가 ARIA 속성을 통해 현재 목록에 표시되는 부분을 이해할 수 있도록 충분한 컨텍스트를 제공하는지 확인해야 합니다.
상태 관리, 가상 DOM 및 컴포넌트 구조
가상 DOM은 만능 해결책으로 오해되는 경우가 많지만, 실제로는 스마트한 차이점 비교 계층일 뿐입니다.React는 UI의 메모리 표현을 유지하고 새 트리를 이전 트리와 비교하여 어떤 DOM 작업이 반드시 필요한지 결정합니다.
그러한 효율성에도 불구하고 각 렌더링 및 차이점 비교에는 여전히 시간이 소요되므로, 목표는 대규모 서브트리가 다시 렌더링되어야 하는 빈도를 최소화하는 것입니다.이곳은 상태 관리, 구성 요소 경계 및 메모이제이션 전략이 모두 교차하는 지점입니다.
먼저 앱의 복잡성에 맞는 적절한 상태 관리 전략을 선택하세요.로컬 React 상태(useState, useReducer)는 작은 구성 요소에 적합하도록 작고 간단한 반면, Redux와 같은 라이브러리나 Zustand와 같은 경량 스토어는 최적화된 구독 패턴을 통해 더 복잡한 전역 상태를 중앙 집중화할 수 있습니다.
둘째, 관련 데이터가 논리적으로 그룹화되도록 상태를 구성하십시오.때로는 여러 가지를 통합해야 할 수도 있습니다. useState 단일 객체를 호출하여 업데이트의 일관성을 유지하는 경우도 있고, 독립적인 관심사가 서로의 재렌더링을 강요하지 않도록 상태를 분할하는 것이 더 효과적인 경우도 있습니다.
이전 값에서 파생된 상태를 업데이트할 때는 항상 함수형 업데이트를 사용하십시오. 등 setCount(prev => prev + 1)배열과 객체를 직접 변경하는 대신 복제하여 불변성을 유지합니다. 이는 예측 가능한 동작을 가능하게 하고 메모이제이션 및 PureComponents와도 잘 어울립니다.
간편한 지침은 국가 차원의 문제를 가능한 한 지역 차원에서 처리하는 것입니다.트리의 상위 계층에 상태 값을 저장할수록 해당 값이 변경될 때마다 더 많은 컴포넌트가 다시 렌더링됩니다. 상태를 실제로 사용하는 컴포넌트로 푸시하면 각 업데이트의 영향 범위를 줄일 수 있습니다.
마지막으로, 큰 구성 요소를 소품이 거의 바뀌지 않는 더 작고 집중된 부분으로 나누세요.메모이제이션된 리프 컴포넌트와 안정적인 속성은 React가 비교해야 하는 가상 DOM의 양을 줄이고 최소한의 DOM 업데이트만으로 경로를 단축합니다.
코드 분할, 지연 로딩 및 향상된 자산 로딩
자바스크립트 번들 크기는 특히 모바일 네트워크에서 성능 저하의 주요 원인 중 하나입니다.React 번들을 다운로드하고 파싱하는 데 몇 초씩 걸린다면, 사용자는 아름다운 UI를 보기도 전에 이탈할 것입니다.
코드 분할 React.lazy Suspense 모든 구성 요소를 미리 배송하는 대신 필요에 따라 구성 요소를 로드함으로써 도움이 됩니다.초기 페이로드에 모든 기능을 포함시키는 대신, 특정 경로 또는 상호 작용에 필요한 부분만 동적으로 가져옵니다.
일반적인 전략은 경로 수준 분할입니다.각 페이지는 자체적인 청크이며 사용자가 해당 페이지로 이동할 때만 로드됩니다. 더 나아가, 큰 기능 구성 요소나 사용 빈도가 낮은 패널을 분할할 수도 있습니다. 단, 이러한 구성 요소들을 별도의 청크로 감싸야 합니다. Suspense 적절한 대체 UI를 제공합니다.
이미지에도 지연 로딩이 적용됩니다.. 첨가 loading="lazy" 에 <img> `tags`는 화면 아래에 있는 이미지가 스크롤되어 화면에 나타날 때까지 로딩을 지연시켜 대역폭을 절약하고 초기 화면 표시 속도를 높입니다. 더 고급 효과를 위해서는 `libraries`와 같은 라이브러리를 사용할 수 있습니다. react-lazy-load-image-component 흐릿한 플레이스홀더와 점진적 로딩을 지원합니다.
코드 분할을 구현할 때는 청크 크기와 사용자 경험 사이의 균형을 맞추는 것이 중요합니다.요청 분할을 과도하게 하면 너무 많은 작은 요청이 생성될 수 있고, 반대로 분할이 부족하면 초기 번들 크기가 커집니다. 지연 평가 컴포넌트 주변에 적절한 대체 기능과 오류 경계를 마련하는 것이 중요합니다. 그래야 네트워크 요청 실패로 인해 앱 전체가 다운되는 것을 방지할 수 있습니다.
서버 측 렌더링, React 서버 컴포넌트 및 서버 액션
서버 측 렌더링(SSR)은 React 앱을 서버에서 렌더링하고 HTML을 클라이언트로 전송하는 기술로, 체감 성능과 SEO를 크게 향상시킬 수 있습니다.사용자는 유용한 콘텐츠를 더 빨리 볼 수 있고, 검색 엔진은 페이지를 더욱 안정적으로 색인화할 수 있습니다.
Next.js와 같은 프레임워크를 사용하면 SSR(서버 사이드 렌더링)과 스트리밍 HTML을 일상적인 앱에서 실용화할 수 있습니다.서버에서 데이터를 가져와서 구성 요소를 HTML로 렌더링하고(때로는 스트림 형태로), 클라이언트에서 해당 마크업에 데이터를 입력하여 상호 작용 가능한 형태로 만듭니다.
기존의 SSR(서버 사이드 렌더링)을 넘어, React 서버 컴포넌트는 UI 로직의 상당 부분을 서버 측으로 옮깁니다.이를 통해 클라이언트로 전혀 전송되지 않는 구성 요소를 렌더링할 수 있습니다. 서버 구성 요소가 데이터베이스나 API를 직접 호출할 수 있으므로 클라이언트 번들 크기를 크게 줄이고 데이터 가져오기를 간소화할 수 있습니다.
서버 액션은 서버에서 실행되지만 클라이언트 구성 요소에서 트리거되는 함수를 정의할 수 있도록 함으로써 이러한 개념을 확장합니다.이렇게 하면 반복적인 REST 엔드포인트나 맞춤형 API 핸들러를 많이 줄일 수 있고, 뮤테이션, 폼 제출 및 기타 상태 저장 작업을 처리하는 방식을 간소화할 수 있습니다.
SSR, 서버 구성 요소 및 서버 액션을 함께 사용하면 다양한 렌더링 전략을 활용할 수 있습니다.핵심 콘텐츠는 서버에서 빠르게 스트리밍할 수 있고, 복잡한 로직은 클라이언트에서 분리되어 있으며, React 런타임이 모든 것을 하나의 일관된 사용자 경험(UX)으로 통합합니다.
웹 워커를 사용하여 부하가 큰 작업을 분산시키세요
아무리 최적화된 React 트리라도 메인 스레드에서 CPU 부하가 큰 작업을 실행하면 버벅거릴 수 있습니다.비용이 많이 드는 연산은 렌더링을 차단하고 이벤트 처리를 지연시켜 앱의 반응성을 떨어뜨립니다.
웹 워커는 이러한 무거운 작업을 백그라운드 스레드로 옮기는 방법을 제공합니다.데이터를 워커에 보내면 워커가 수치를 계산하거나 대규모 데이터 세트를 처리하고, 메시지 전달을 통해 결과를 다시 받게 되므로 메인 스레드는 UI 업데이트를 처리할 수 있습니다.
웹 워커의 일반적인 작업 부하에는 데이터 처리, 이미지 처리, 실시간 분석 또는 복잡한 시뮬레이션이 포함됩니다.예를 들어, 웹 스택으로 개발된 게임은 핵심 게임 로직을 워커 스레드에 위임하고 메인 스레드는 렌더링 및 입력 처리에 전념하는 경우가 많습니다.
React에 워커를 통합하려면 별도의 스크립트 파일을 만들어 특정 이벤트를 수신해야 합니다. onmessage 워커 내부에서 구성 요소로부터 메시지를 게시합니다.컴포넌트에서는 워커를 인스턴스화하고 입력값을 전달합니다. postMessage 응답이 있을 때 상태를 업데이트하고, 이상적으로는 컴포넌트가 마운트 해제될 때 워커를 정리합니다.
Comlink, workerize 또는 bundler 플러그인과 같은 라이브러리를 사용하면 이러한 패턴을 간소화할 수 있습니다. 저수준 메시지 전달을 추상화하고 비동기 함수를 호출하는 것처럼 느껴지는 API를 제공함으로써 React 코드베이스에서 더 쉽게 이해할 수 있도록 합니다.
주목해야 할 주요 브라우저 및 사용자 중심 지표
더 높은 차원에서, 전반적인 웹 성능은 일반적으로 사용자 중심 지표를 사용하여 추적됩니다. 예를 들어, 최초 콘텐츠 표시 시간(FCP), 최대 콘텐츠 표시 시간(LCP), 상호 작용 시간(TTI) 등이 있습니다. 이러한 지표는 사용자가 콘텐츠를 얼마나 빨리 보고 실제로 상호 작용할 수 있는지 파악하는 데 도움이 됩니다.
정상적인 React 앱은 일반적인 기기에서 FCP가 약 1.8초 미만, LCP가 약 2.5초 미만, TTI가 4초 미만인 것을 목표로 합니다.정확한 임계값은 프로젝트에 따라 다를 수 있습니다. 이러한 수치를 지속적으로 초과하는 경우 번들, 렌더링 전략 또는 서버 응답 시간에 개선이 필요하다는 신호입니다.
Lighthouse, WebPageTest, Chrome의 성능 패널과 같은 도구를 사용하면 가상 테스트 환경에서 이러한 지표를 측정할 수 있습니다.실제적인 통찰력을 얻으려면 SpeedCurve, Datadog, LogRocket 또는 Sentry와 같은 실사용자 모니터링(RUM) 도구를 사용하여 실제 사용자 세션을 추적하고 속도 저하의 원인을 코드 변경 사항과 연결할 수 있습니다.
React 자체의 프로파일러 API는 이 그림과 깔끔하게 통합됩니다.나무의 일부를 감싸는 데 사용할 수 있습니다. <Profiler>느린 렌더링을 기록하고 특정 사용자 흐름과 연관시킬 수 있습니다. 백엔드 및 네트워크 모니터링과 함께 사용하면 성능에 대한 완벽한 엔드투엔드 관점을 얻을 수 있습니다.
성능 튜닝을 위한 실용적인 팀 워크플로
실제 프로젝트에서 성능 튜닝은 일회성 정리 작업이 아니라 반복 가능한 워크플로로 처리할 때 가장 효과적입니다.간단한 4단계 순환 과정(식별, 조사, 실행, 확인)은 무작위적인 세부 최적화를 방지하고 중요한 부분에 노력을 집중할 수 있도록 도와줍니다.
식별이란 프로파일러, 지표 및 사용자 보고서를 활용하여 구체적인 증상을 찾아내는 것을 의미합니다. 페이지 로딩 속도 저하, 프레임률 저하 또는 특정 흐름에서의 높은 이탈률과 같은 문제를 파악해야 합니다. 직감이 아닌 측정 가능한 문제를 찾아야 합니다.
조사는 근본 원인을 파헤칩니다예를 들어 페이지에 수십 개의 숨겨진 iframe이 포함되어 있거나, 특정 컴포넌트가 너무 자주 다시 렌더링되거나, 모든 경로에서 대규모 벤더 라이브러리가 로드되는 경우입니다. 이럴 때는 React DevTools 프로파일러와 Chrome 타임라인을 적극적으로 활용해야 합니다.
구현 단계에서는 목표에 맞춘 수정 사항을 적용합니다.—자주 사용되는 컴포넌트의 메모이제이션, 긴 목록의 가상화, 번들 분할, 웹 워커로 작업 오프로드, 특정 페이지에 대한 SSR 활성화 등. 각 변경 사항은 논리적으로 판단할 수 있을 만큼 작아야 합니다.
확인 절차는 마지막 단계이며, 종종 가장 간과되는 단계입니다.프로파일링 시나리오를 다시 실행하고 메트릭 대시보드를 확인하여 변경 사항이 실제로 수치를 개선했는지, 시스템의 다른 부분에서 회귀를 일으키지 않았는지 확인합니다.
적절한 React 빌드, 신중한 메모이제이션, 불변 상태 관리, 리스트 가상화, 전략적인 코드 분할, SSR, 웹 워커, 그리고 지속적인 측정을 결합하면, 복잡해지더라도 빠르고 반응성이 뛰어난 React 애플리케이션을 만들 수 있습니다.위의 기술들은 시기상조의 미세 조정이 아니라, 성능이 끊임없는 문제 해결 과정이 아닌 자연스러운 결과물로 남도록 하는 아키텍처를 구축하는 것에 관한 것입니다.

