본 포스트는 Next.js 기반 프로젝트의 트리셰이킹 진행 과정을 따라할 수 있도록 설명과 함께 정리한 글입니다. 비록 가능한 모든 최적화를 진행하지는 못했지만, 방법의 문제는 아니었으며 결과적으로 가장 중요한 페이지의 로딩 속도를 30% 가량 줄일 수 있었습니다.
😅 사실 30%라는 엄청난 수치를 기록할 수 있었던 것은 그동한 빠르게 프로덕트를 빌드하는 동안 최적화가 거의 되어있지 않았기 때문입니다.
속도를 개선하겠다는 막연한 목표를 조금 더 구체화 하기 위해서 시도해 볼 수 있는 최적화의 종류를 팀원들과 공유하고 방향성을 결정하였습니다. 다음과 같이 세 종류의 최적화에 대해 검토하고 논의하였습니다.
결과적으로는 제목에서도 볼 수 있듯, 빌드 시간과 렌더링(스크립팅) 시간에 영향을 줄 수 있는 패키지 최적화를 선택하였습니다. 렌더링 최적화나 로직 최적화는 이미 코드를 작성할 때 리뷰에서 신경쓰고 있는 부분이었기도 했고, 모든 코드를 재검토하기에는 시간이 너무 많이 들어갈 것으로 예상했기 때문입니다.
@next/bundle-analyzer
🌟
→ Next.js 빌드 프로세스에서 번들링된 패키지 크기를 시각화하기 위하여 사용합니다. 바로 아래에 등장하는 알록달록한 사이즈 시각화 결과가 해당 패키지로부터 나온 결과물입니다.
@next/bundle-analyzer
Chrome 개발자 도구
→ 성능 지표 측정에 사용하였습니다. 정량화된 최적화를 위해서 Performance 탭에서 스크립팅 시간의 변화를 측정하였습니다.
State of speed tooling
~~webpack-bundle-analyzer
→ Webpack을 이용하는 프로젝트에서 @next/bundle-analyzer
와 같은 목적으로 사용할 수 있는 패키지라고 합니다. 하지만 Next.js 프로젝트에서 오동작이 있어서 사용하지 않았습니다. 직접적인 에러는 발생하지 않았지만, 실제와 다소 다른 결과를 얻었습니다. 패키지 자체의 문제보다는 최적화를 진행한 프로덕트의 custom next configuration과의 충돌일 가능성도 배제할 수 없었습니다. 바로 곧이어 시도해 본 @next/bundle-analyzer
가 훌륭하게 동작했기 때문에 사용하지 않았습니다.
webpack-bundle-analyzer
우선 최적화 할 대상을 찾기 위하여 @next/bundle-analyzer
를 이용해서 프로젝트 웹페이지에서 사용하고 있는 코드들의 사이즈를 측정 해 보았습니다. Next.js 프로젝트와 함께 사용하기 위하여 next.config.js
안에서 다음과 같이 bundle analyzer를 integrate 해 주었습니다.
// next.config.js
// ANALYZE=true 입력이 들어왔을 때만 사용
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
...
module.exports = withBundleAnalyzer(withSass(
...
그 다음, 쉘에서 다음과 같이 실행하여 정상 동작을 확인한 뒤:
$ ANALYZE=true yarn build
조치를 취하며 계속해서 성능을 확인하기 위해 package.json에 yarn 명령어를 추가하여 간편하게 다시 해당 화면을 볼 수 있도록 하였습니다.
// in package.json
"analyze-bundle": "ANALYZE=true yarn build"
// Execute by $ yarn analyze-bundle
위와 같은 분석 결과에 나타나는 용량에 Stat, Parsed, 그리고 Gzipped 세 가지 옵션이 존재하는 것을 확인할 수 있습니다. 이 옵션은 각 번들의 사이즈 측정 기준으로써, Stat은 빌드된 그대로의 상태, Parsed는 웹팩이 트리셰이킹을 마친 결과물, Gzipped는 결과적으로 서빙을 위해 압축된 사이즈입니다. Parsed를 줄이면 자연스레 Gzipped는 비례하여 줄어듦으로, Parsed size를 기본으로 줄이기를 시도하는 것이 좋습니다.
프론트엔드 패키지 사이즈에 영향을 주는 상위 10개 번들은 다음과 같았습니다. 각각의 패키지에 대해 적절한 조치를 취할 수 있는 경우 작업을 진행하였습니다.
상위 10개 패키지 이외에도 덩어리 크기가 제법 크고 조치가 가능한 것으로 보인 패키지도 두 개 있었습니다.
Highlight.js는 웹 상에서 코드를 보여줄 때 IDE처럼 키워드들을 하이라이팅 하는 용도로 사용되는 패키지입니다. 그런데 굉장히 많은 언어를 지원하다 보니, 패키지 전체를 사용하게 되면 전체 패키지 사이즈가 1MB를 넘어가게 됩니다. 전체 패키지를 그대로 사용하는 대신 필요한 언어만 import해서 사용하게 하면 용량을 크게 줄일 수 있습니다. 아래 포스트에서 95% 가까히 용량을 줄인 방법을 소개하고 있습니다. Reducing bundle size of Highlight.js with Webpack
위 포스트의 방법을 천천히 따라하니 다음과 같이 최적화가 깔끔하게 진행되어 기존 패키지 사이즈 대비 95% 감소하는 결과가 나왔습니다.
위 두 패키지 모두 실제로 사용하지 않는 패키지였기에 바로 제거할 수 있었습니다. 아무것도 안하고 $ yarn remove
만으로 끝이 나서 길가다 돈 주운 기분으로 제거하였습니다.
Code mirror는 Ace 에디터 사용 전에 사용하던 코드 에디터로, 이제는 준 레거시가 된 컴포넌트에서만 사용되고 있었습니다. 에디터의 기능을 사용하지 않고 있어서 textarea로 리팩토링한 뒤 패키지를 제거하였습니다.
lodash는 유명하고 기능이 많은 라이브러리인 만큼 많은 내용을 담고 있고, 번들링 사이즈도 상당했습니다. Import 방식을 변경함으로써 트리셰이킹의 효율을 높일 수 있다는 글을 보고 트리셰이킹을 시도하였고, 결과도 좋게 나왔습니다.
하지만 배포를 위한 최종 빌드에서 lodash treeshaking과 관련된 변경사항들이 에러를 만들며 빌드에 실패했습니다. Lodash의 import 방식을 바꾸기 위해서는 lodash-es 패키지를 사용해야 했는데, lodash-es 패키지가 node 14 이상에서 빌드 가능했기 때문에 생긴 문제였습니다. nvm으로 프로젝트의 node 버전 업데이트를 시도했으나 기존 프로젝트에 node 12 dependency가 있어 lodash 최적환느 포기하였습니다.
총 Chunk Size: 8.63MB → 6.78MB (-21.4%)
결과적으로 크롬에서 측정한 scripting time이 모든 페이지에 걸쳐 개선되었으며, 특히 제거된 스크립트에 영향을 많이 받을 것으로 예상되던 페이지의 경우에는 30%에 가까운 성능 개선을 보여주기도 했습니다.
시간 관련 라이브러리다 보니 프로덕트 전반에 걸쳐 중요한 로직에 굉장히 많이 사용된 패키지였습니다. 백엔드의 데이터 포맷과도 연관되어 있기 때문에 쉽사리 제거할 수 없어 우선은 조치를 보류하였습니다. 패키지 크기도 문제지만 deprecate된 라이브러리이기 때문에 대대적인 공사가 가능한 시점에 제거할 수 있길 바라며 우선은 넘어가게 되었습니다.
크기가 큰 패키지로써 눈에 띄는 ace.js, katex.js, markdowntoolbar.js, quill.js는 모두 텍스트 에디터와 관련된 패키지들입니다. 프로덕트의 특성상 커뮤니티 게시글 작성 에디터, 코딩 실습창용 에디터 등 다양한 에디터가 있기에 발생한 결과였습니다. 하지만 에디터 관련 코드들의 대부분이 단시간에 제거하기 어려운 레거시들이 첩첩산중으로 쌓여 있어서 다음 기회에 진행하기로 하였습니다.
마이리얼트립 성능 최적화 포스트
마이리얼트립 웹사이트 성능 측정 및 최적화 Part 1. 리소스 로딩
Why speed matters?
Why does speed matter?
bundle-analysis를 이용한 패키지 크기 측정
Next 배포를 위한 준비(bundle-analyzer, tree shaking, gzip)
불필요한 패키지 사용하지 않기 (Tree-shaking)
Decrease webpack bunlding file size
번들 사이즈 최적화
6 tips to optimize bundle size
Lodash treeshaking
트리 쉐이킹으로 자바스크립트 페이로드 줄이기
정말 큰 도움이 되었습니다 ㅎㅎ
감사합니다 !