Skip to main content

Command Palette

Search for a command to run...

React App 빌드 최적화로 First Load JS 줄이기

Updated
•3 min read•View as Markdown

Nextjs로 개발한 프로젝트를 빌드하면 페이지별 용량 정보를 확인할 수 있다.

한 줄에 각 페이지에 대한 정보가 표시된다. 오른쪽에 두개의 용량이 표시되는데, 차례대로 페이지 크기, 최초에 로딩해야하는 자바스크립트의 크기이다.

아래에는 모든 페이지에서 공통으로 필요한 자바스크립트의 크기를 보여주고, 이 자바스크립트가 어떤 파일들로 구성되어있는지도 확인할 수 있다. 이 정보만으론 자세한 분석이 힘들어서, 각 파일이 어떤 모듈로 구성되어있는지, 그리고 각 모듈의 대략적인 크기가 얼마인지를 시각적으로 확인하기 위해서 bundle-analyzer를 추가로 사용할 수 있다. (Docs)

설명대로 환경변수를 세팅한 후 next app을 빌드하면 세 개의 탭이 열리는데, 그중 클라이언트 탭을 확인한다.

왼쪽 상단의 아이콘을 누르면 필터링을 위한 UI가 나오는데, 여기서 빌드 결과 생성된 위의 파일들을 하나씩 확인해보면 main과 framework파일은 각각 Next.js와 React임을 알 수 있다.

그러므로 이 두 파일에서 용량을 줄일 수는 없고, 아래에 있는 app에서 용량을 줄여야 한다. app만 필터링해서 확인해보면, 전체 페이지에서 공통적으로 사용되는 모듈들이 크기별로 구분되어 표시된다.

Dynamic import

Next.js에서는 React.lazy와 Suspense를 합친 next/dynamic모듈을 제공한다. 이 모듈을 사용하면 클라이언트에서 컴포넌트가 마운트되는 시점까지 자바스크립트 파일 로딩을 미룰 수 있다.

데브툴의 네트워크 탭에서 쓰로틀을 걸어보거나, 자바스크립트 파일 로드 목록을 살펴보면 dynamic import적용 여부를 확인할 수 있다. 이렇게되면 번들 용량도 줄어들게 된다. (이전보다 7KB 감소)

트리셰이킹

라이브러리마다 트리셰이킹을 제공하는지 조사해본다.

빌드 분석 결과에선 Sentry모듈의 용량이 꽤 큰데, Sentry Docs에는 트리셰이킹을 적용하기 위한 방법이 설명되어 있다. config파일에서 debug, tracing 플래그를 false로 세팅해주니 약 10KB의 용량이 줄었다.

config 파일을 커스텀하여 용량을 줄일 수도 있다. 예를들어 sentry를 앱에 설치하면 기본적으로 40KB짜리 Replay integration이 추가되는데, 필요하지 않다면 지우면 된다.

이 외에도 프로덕션에서 config가능한 다른 라이브러리가 있다면 Docs에 번들링 용량을 줄일 방법이 있을 수도 있다.

불필요한 모듈이 import되는지 확인하기

불필요한 모듈까지 import하는 경우는, 여러 모듈을 re-export하는 목적으로 만들어진 파일(Barrel File)을 import하는 경우에 발생할 수 있는 문제이다.

위 예시처럼 mod/index.ts에서 a, b, c를 re-export할 때, mod로부터 import를 시도하면, mod/a.ts에 있는 heavy라이브러리까지 같이 import를 하게 된다.

불필요한 다른 모듈이 import되지 않도록, 하위디렉토리까지 명시하여, mod/b.ts와 mod/c.ts에서 b와 c를 각각 import하면 heavy를 불러오지 않을 수 있다. (cherry-picking)

나의 경우, 앱에서 랜덤한 닉네임을 생성하기 위해 라이브러리를 사용했는데, 닉네임을 생성하는 페이지 이외에는 해당 라이브러리가 쓰이지 않았지만 위에서 언급한 이유로 import되고 있음을 발견했다. 라이브러리가 필요한 페이지에서만 import되도록 코드를 변경했더니, 약 20KB의 용량이 줄어들었다.

비슷한 원인으로 불필요하게 import되고 있던 다른 컴포넌트들을 위와 같은 방식으로 개선했더니 용량을 더 줄일 수 있었다.

관련 이슈에서도 비슷한 문제가 언급되고 있으며, Barrel import를 금지하는 린트 룰도 있다.

라이브러리 교체하기

동일한 기능을 제공하지만 용량이 다른 라이브러리가 있을 수 있다. 용량이 많이 나가는만큼 더 많은 기능을 제공할 수도 있지만, 다양한 기능들을 사용하지 않는다면 불필요하게 용량만 차지하는 셈이다.

내 경우 html문자열에서 태그를 제거하는 라이브러리가 필요했는데, 처음에 다운로드 받은 string-strip-html이란 라이브러리는 용량이 37KB로, 필요한 기능에 비해 용량이 너무 컸다.

라이브러리에서 제공하는 함수의 반환값을 확인해보니, 실제로 필요한 result프로퍼티 이외에 불필요한 데이터들이 많이 보였다. 이런 부가적인 기능을 제공하는것도 용량이 높아진 원인이 됐을 것이다.

필요한 기능을 제공하는 다른 라이브러리를 찾아봤더니, 단순하게 태그만 제거해주는 striptags라이브러리가 있었다. 기존 라이브러리는 용량이 37KB인데, 바꾼 라이브러리의 용량은 바이트 단위였다.

결과적으로 약 40KB정도를 줄일 수 있었다.


Dynamic import, 트리셰이킹, import최적화, 라이브러리 교체 등의 방법으로 번들 용량을 360KB에서 230KB로, 약 35%를 줄일 수 있었다.

bundle-analyzer와 같은 시각화 도구가 도움이 많이 되었다. 코드만 보고 최적화를 시도했다면 시간이 배로 걸렸을 것 같다.


++

이후에도 최적화 작업으로 30KB 더 감소시켰는데(200KB), 중간에 추가된 라이브러리로 15KB가 늘어나서 현재는 215KB로 마무리된 상태다.

More from this blog

(오픈소스 분석) Radix DismissableLayer

@radix-ui/react-dismissable-layer 라이브러리 코드를 살펴본다. 용도 현재 레이어와 분리된 UI 레이어를 렌더링할 수 있고, 외부 레이어와 인터랙션 할 때 제거할 수 있는 컴포넌트이다. 이와 같이 열고 닫는 방식의 사용 방법은 모달이나 팝오버 같은 UI에 응용할 수 있다. Context DismissableLayer의 컨텍스트는 내부 상태를 저장하는 용도로 사용되지 않고, 렌더링된 모든 DismissableLayer엘리...

Mar 27, 20247 min read

(오픈소스 분석) Radix Checkbox

@radix-ui/react-checkbox 라이브러리 코드를 살펴본다. 추가적으로 WAI-ARIA에서 소개하는 체크박스 패턴에 대해 알아본다. Checkbox Pattern (WAI-ARIA) 여기에서 자세한 내용을 확인할 수 있다. WAI-ARIA의 Checkbox Pattern에선 Accessible한 UI를 만들기 위한 어트리뷰트 및 키보드 인터랙션 가이드와 체크박스의 타입을 소개한다. 여기서 알아볼 것은 체크박스의 타입이다. 두 가지 ...

Feb 18, 20244 min read

Untitled Publication

14 posts