LCP 개선 - boostcampwm-2022/web33-Mildo GitHub Wiki
Mildo의 초기 성능(배포 환경)
- chrome 개발자 도구의 lighthouse를 활용하여 Mildo의 성능을 측정한 결과
테스크톱 환경에서는 88점
,모바일 환경에서는 58점
인 것으로 나타났다.- lighthouse 성능 점수가 실제 성능을 보장한다고 볼 수는 없으나, 일부 팀원이 실제로 모바일 환경에서 성능 저하를 경험했다.
- 그래서 모바일 환경 기준
15.4초
로 가장 성능이 가장 낮은Largest Contentful Paint
항목을 집중적으로 개선하기로 하였다.
데스크톱 환경
모바일 환경
코드 스플리팅으로 bundle.js 사이즈 개선(개발 환경)
- 우선 LCP의 세부 내역을 확인한 결과
Reduce unused JavaScript
의 시간이 5.85초로 매우 오래 걸렸는데, 가장 큰 요인은 cra로 build한bundle.js
의 크기가 무려835.1KiB
였다.
- 이렇게
bundle.js
가 비정상적으로 큰 이유 중 하나가 외부 라이브러리이다. 우리는로그인 모달
과24시간 인구 그래프
를 구현하며 각각 react-icons와 apexCharts를 사용했는데, 이것의 용량이 매우 컸던 것이다.
-
그래서 React에서 제공하는
lazy
를 활용하여, 크기가 큰로그인 모달
과24시간 인구 그래프
를 스플리팅하였다.loadable
등 코드 스플리팅 외부 라이브러리를 사용하지 않고React lazy
를 활용한 이유는- 컴포넌트를 쉽게
동적 import
할 수 있으며, - 이미 외부 라이브러리로
bundle.js
크기가 매우 크기 때문이다.
- 컴포넌트를 쉽게
// client/src/components/infoDetailModal/infoDetailModal.tsx ... const SecondLevelComponent = lazy( () => import('../SecondLevelComponent/SecondLevelComponent') ); ... return ( ... {isSecondLevel && (<SecondLevelComponent ... />)} ... );
-
SecondLevelComponent
가 렌더될 때 네트워크 탭에서 별도의 build 파일을 받아오는 것을 확인할 수 있다.
- 또한,
lazy
로 코드 스플리팅을 진행한 결과Reduce unused JavaScript
의 시간이 5.85초에서4.11초
로 1.74초 감소하였고,bundle.js
의 용량이 835.1KiB에서465.9KiB
로 369.2KiB 감소하였다.
-
하지만 처음엔 더 오래 걸렸다.- 코드 스플리팅 작업 직후에 스플리팅된 파일을 바로 받아와 오히려
Reduce unused JavaScript
의 시간이 훨씬 더 오래 걸렸다.
-
이는 우리가 최초 코드에서
로그인 모달
을 제한 조건 없이 바로 가져왔기 때문이다. 아래와 같이 제한 조건을 설정한 후 성능이 바로 개선되었다.... const LoginModal = lazy(() => import('../../components/LoginModal/LoginModal')); ... const SecondLevelComponent = lazy( () => import('../SecondLevelComponent/SecondLevelComponent') ); return ( <StyledMainPage> <Map latitude={coordinates!.latitude} longitude={coordinates!.longitude} /> <SearchBarAndMyBtn /> <DensityFilterList /> <InfoDetailModal /> {isLoginModalOpen && <LoginModal />} <MyInfoSideBar /> </StyledMainPage> ); ...
- 코드 스플리팅 작업 직후에 스플리팅된 파일을 바로 받아와 오히려
-
코드 스플리팅이 무조건 시간을 줄여주는 것은 아니다.- 혹시 몰라 크기가 작은 모달들도
동적 import
를 해봤는데, 오히려bundle.js
의 크기와Reduce unused JavaScript
의 시간만 살짝 늘어났다. 오히려 작은 것들은 하나의 파일로 받아오는 것이 훨씬 효율적이라는 것을 알 수 있었다.
- 혹시 몰라 크기가 작은 모달들도
-
그러나 여전히
bundle.js
의 크기가 너무 컸다. 가장 많은 용량을 차지하는 모듈은 apexCharts였다.
cra-bundle-analyzer로 분석한 bundle.js의 크기 구성
- 이를 위한 해결 방법으로는 apexCharts 라이브러리를 사용하지 않거나, 다른 build 방법을 모색하는 것이 있다.
CRA에서 vite로 번들러 마이그레이션(개발 환경)
-
24시간 인구 그래프
가 apexCharts를 매우 의존하고 있어 대체하기가 어려웠다. -
그래서 build 도구를
vite
로 변경하기로 결정하였다. 이유는 cra보다bundle.js
파일의 크기를 크게 줄여줄 것이라고 기대했고, cra로 생성한 프로젝트도vite
로 쉽게 migration 할 수 있을 것으로 판단했기 때문이다. -
vite.config.js
파일 생성을 위해 기본 라이브러리들을 설치하였고, 기본 환경을 세팅하였다.// client/vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import viteCompression from 'vite-plugin-compression'; export default defineConfig({ plugins: [react(), viteCompression()], server: { open: true, port: 3000 } });
-
vite 적용 후 cra 대비
Reduce unused JavaScript
의 시간이 4.11초에서3.6초
로 0.51초 감소하였고, 빌드된번들 파일
의 크기는 465.9KiB에서104.2KiB
로 361.7KiB 감소하였다.
- 최종적으로 MainPage도 스플리팅을 진행하였고, 개발에 사용되는 chrome의 확장 프로그램을 중지 시켜
Reduce unused JavaScript
는0.3초
로,번들 파일
은65.6KiB
로 감소하였다.
-
그러나 여전히 LCP의 다른 부분에서 시간 지연이 발생하는 것으로 나타난다.
-
사실 몇 가지 어려움이 있었다-
vite
적용 후 vscode가 네이버 maps api에서 제공하는 namespacenaver
를 인식하지 못했는데,vite
설정 과정에서tsconfing
의types 옵션
을 함부로 변경해서 그런 것이었다.→
tsconfig
옵션을 다시 되돌려 해결했다. -
vite
적용 직후에는 LCP 시간이 비정상적으로 증가하였다. 알고 보니vite
의 개발 환경과 배포 환경의 차이었고, build 후 실행하여 배포 환경과 유사하게 조성하니 정상적으로 시간이 감소하였다.
-
기타 개선 사항(개발 환경)
- Preload Largest Contentful Paint Image와 Eliminate render-blocking resources, Enable text compression을 중점적으로 개선하고자 했다.
Preload Largest Contentful Paint Image
-
Mildo에서
Preload Largest Contentful Paint Image
가 오래 걸리는 핵심적인 이유는 네이버 maps api를 통해 지도 이미지를 매우 많이 불러오기 때문이다. -
모바일 환경에서도 10장이 넘는 사진을 한 번에 받아온다.
-
Preload Largest Contentful Paint Image
의 시간을 줄이는 방법 중 Mildo의 상황에 가장 적합한 것은 브라우저가 이미지의 크기를 미리 정할 수 있게 각 이미지 태그에 preload 설정을 걸어주는 것이다. -
그러나 네이버 maps api의 지도 이미지를 직접 컨트롤할 방법이 없어 근본적인 문제해결이 불가능했다.
-
그래서 최대한 지도 이미지를 적게 불러오고자 네이버 지도의 기본 줌을 14에서 16으로 축소하였고,
Preload Largest Contentful Paint Image
시간이 3.13초에서2.17초
로 0.96초 감소하였다.
Eliminate render-blocking resources
-
index.html에서 네이버 maps api
script 태그
가 다른 DOM의 렌더링을 차단하여 시간이 지연되어 발생하는 시간이다. -
이에 해당
script 태그
에defer
속성을 추가하였고, 이script 태그
와 상관없이 페이지 내 다른 DOM이 생성되게 만들어Eliminate render-blocing resources
문제를 완전히 해결하였다.
Enable text compression(배포 환경)
-
실제 배포 환경에서 lighthouse 성능을 다시 측정하였는데,
Enable text compression
라는 새로운 문제와 함께 번들 파일의 크기가307.1KiB
로 커진 문제가 발생하였다. -
이에 번들 파일의
Response Headers
를 확인하니Content-Encoding
헤더가 비어있었고, 번들 파일이 제대로 압축되지 않았다는 것을 알 수 있었다. -
그래서 번들 파일을 제공하는
nginx
서버에서 압축을 위해gzip
설정을 사용하였다.- /etc/nginx/nginx.config
-
번들 파일의
Response Headers
에서Content-Encoding
헤더의gzip
이 제대로 설정된 것을 확인할 수 있다. -
결과적으로 압축을 통해
Enable text compression
문제를 해결하였고, 번들 파일의 크기가 정상적으로 돌아왔다.이 과정에서 MainPage를 다시 번들 파일로 합쳐 용량이 살짝 증가했다.
결과(배포 환경)
- 최종적으로 배포 환경에서 데스크톱 환경의 점수는
98점
으로 10점 증가했고, 모바일 환경의 점수는76점
으로 18점 증가했다. - 특히, 모바일 환경에서
Largest Contentful Paint
시간이6.6초
로 이전 대비 8.8초나 감소하였고, 다른 세부 항목들도 함께 개선된 것을 확인할 수 있었다.
-
다만, 여전히
Preload Largest Contentful Paint Image
로 인한 지연이 발생하고 있어 추후에 개선이 필요해 보인다. -
그래도 코드 스플리팅과
vite
를 통해 처음에 의도했던 바와 같이Reduce unused JavaScript
의 시간을 매우 크게 줄였고, 성능 개선에도 유의미한 성과가 있었다.