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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887518898/a22fbb18-93b2-4935-af7b-98fb919e7f86.png align="center")

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

아래에는 모든 페이지에서 공통으로 필요한 자바스크립트의 크기를 보여주고, 이 자바스크립트가 어떤 파일들로 구성되어있는지도 확인할 수 있다. 이 정보만으론 자세한 분석이 힘들어서, 각 파일이 어떤 모듈로 구성되어있는지, 그리고 각 모듈의 대략적인 크기가 얼마인지를 시각적으로 확인하기 위해서 `bundle-analyzer`를 추가로 사용할 수 있다. ([Docs](https://nextjs.org/docs/pages/building-your-application/deploying/production-checklist#reducing-javascript-size))

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887527478/670d507b-9116-4d4f-8243-019bebcd3d49.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887531170/212b2d31-a094-4cdf-a27f-6fc61da0b37a.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887534330/4afb64f6-743b-407c-bd75-c44cbcce6588.png align="center")

### Dynamic import

Next.js에서는 `React.lazy`와 `Suspense`를 합친 [next/dynamic](https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#nextdynamic)모듈을 제공한다. 이 모듈을 사용하면 클라이언트에서 컴포넌트가 마운트되는 시점까지 자바스크립트 파일 로딩을 미룰 수 있다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694889385564/e010cfcd-a6d5-4383-9553-f36cf1a154a5.gif align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694889405806/d8f58dc9-3eb7-4534-9639-659ab4325ebf.png align="center")

### 트리셰이킹

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887538164/3ef8ab33-21b2-47a0-b312-bc85c13209b5.png align="center")

빌드 분석 결과에선 Sentry모듈의 용량이 꽤 큰데, [Sentry Docs](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/)에는 트리셰이킹을 적용하기 위한 방법이 설명되어 있다. config파일에서 debug, tracing 플래그를 false로 세팅해주니 약 10KB의 용량이 줄었다.

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

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

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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887543581/e27daf1b-2fc9-4559-adac-5bf139931db7.png align="center")

위 예시처럼 `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)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887550424/63a923a6-4bb2-44ef-862d-43f49fd9daa2.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887553161/59e421d2-0736-4c69-b3b7-3a246347e69f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887558902/cfa916f9-56cb-4301-83e1-cbcfc563a90e.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887561710/476b4809-2ead-4469-8eb2-0c48824529bd.png align="center")

[관련 이슈](https://github.com/vercel/next.js/issues/12557)에서도 비슷한 문제가 언급되고 있으며, [Barrel import를 금지하는 린트 룰](https://github.com/gajus/eslint-plugin-canonical#no-barrel-import)도 있다.

### 라이브러리 교체하기

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

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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887565854/4c2b839d-76a0-4f07-930b-730c2b624426.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887570634/17a7ba3d-494e-4915-8a1b-56d20fc23429.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887567879/9fabe085-cdbf-4f43-ad7f-878d01179e25.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694887574205/703bc508-e993-438b-ace3-2b9d3569fdf8.png align="center")

---

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

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

---

++

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699354428716/f3a83fb8-f47f-4cc1-812e-0929bbaf4d43.png align="center")
