DevOps/Optimization

[최적화] Lighthouse 퍼포먼스 성능 55점 향상 방법 (feat. 리액트)

닝닝깅 2023. 12. 17. 01:06

리액트와 파이어베이스 DB를 사용하여 테니스 커뮤니티 프로젝트를 진행하였다.

 

기능 구현을 마치고 최적화 단계에 들어섰고, 처음으로 경험해보았던 최적화 과정이었기 때문에 나름 고군분투를 겪어 이와 관련된 내용을 정리해보고자 글을 작성하게 되었다.

 

😥 처참한 초기 성능

Lighthouse를 사용하여 성능 평가를 하였다.

결과는 처참.. 기능 구현에 초점을 맞춰 프로젝트를 진행하다보니 이런 처참한 결과가 나온 것 같다. (ㅠㅠ..)

여러 페이지가 있지만 일단 가장 맨 처음 보이는 메인 페이지를 기준으로 성능 최적화를 진행하였다.

이 게시물에서는 Lighthouse의 performance만 살펴볼 예정이다. accessibility는 다음 게시물에..!

 

 

👁‍🗨 현상 파악

성능 최적화 = 로딩 최적화 + 렌더링 최적화

 

로딩 성능은 각 리소스를 불러오는 성능이고, 렌더링 성능은 불러온 리소스를 화면에 보여주는 성능이다.

 

나도 크게 로딩 최적화와 렌더링 최적화로 나누어 최적화를 진행해보았다.

 

처음에는 목표 점수를 세워 도달하는 방향으로 하려고 했으나

성능은 실행환경의 영향도 많이 받아 절대적으로 좋은 성능이란 없다는 점과

비슷한 커뮤니티 서비스의 성능 점수가 70~80점대로 그리 높지 않다는 점을 고려하여 그냥 최대한 할 수 있는 건 다 해보기로 하였다.

 

순서대로 진행한 것은 아니기 때문에 결과 자료의 전후 차이를 잘 확인해주어야 한다.

 

✅ 로딩 최적화

💬 방법 1.  코드 스플리팅

원인

CRA를 사용하여 리액트 프로젝트를 시작하면 webpack이 함께 설치되어 여러 파일들을 하나의 번들 파일 묶여 내보내진다. 최적화를 위해 수행되는 것이지만 프로젝트의 규모가 커지고 번들의 크기가 커지게 될 경우 초기 로딩 속도를 저하시키는 일이 발생한다.

 

이럴 경우 하나의 번들 파일을 나누는 코드 스플리팅을 하면 된다.  

 

코드 스플리팅을 할 경우 하나의 번들 파일을 여러개의 번들 파일로 나눈 뒤 실제 로드될 화면에 필요한 번들 파일만 불러오고 나머지 번들 파일은 호출하지 않고 지연시킴으로써 작업량을 줄여 더 빠른 속도로 화면이 보일 수 있게 할 수 있다.

 

 

개선 방법

React.lazy을 활용하여 Route를 기준으로 코드 스플리팅을 한다.

 

 

개선 결과

개선 전 점수와 개선 후 점수의 차이이다.

 

리소스 크기도 줄어든 것을 볼 수 있다.

 

 

💬 방법 2. react-icons 번들 사이즈 축소

원인

이 프로젝트에는 아이콘이 많이 들어갔는데 대부분 react-icons 라이브러리를 사용하여 구현하곤 했다.

react-icons을 통해 하나의 아이콘을 사용하기 위해서는 해당 종류의 아이콘이 포함된 js 파일 전체를 불러와야 한다.

그래서 빌드 시 chunk 사이즈가 커지게 된다.

 

개선 방법

이를 해결하기 위해 @react-icons/all-files 라이브러리를 사용하였다.

이 라이브러리는 아이콘 별로 자바스크립트 파일을 별도로 가지고 있기 때문에,

빌드 시 트리쉐이킹 방식으로 더 적은 크기의 chunk를 만들 수 있다.

 

주의사항

드물게 react-icons 라이브러리에는 포함되어 있으나 이 라이브러리에는 포함되지 않은 아이콘이 존재할 수 있다.

 

개선 결과

개선 전 점수와 개선 후 점수의 차이이다.

 

리소스의 크기가 5 MB정도 줄어들었다.

 

💬 방법 3. 외부 리소스 preconnect

원인

이 프로젝트에서는 구글 폰트와 파이어베이스에 연결을 하여 리소스를 가져온다.

브라우저가 외부 도메인에서 리소스를 받아오기 위해서는 DNS, TCP, TLS 왕복 등의 단계를 거치게 되는데, 데이터를 받아올 일이 생겼을 때 이 단계를 처음부터 수행하게 된다면 시간이 소요된다.

 

개선 방법

preconnect를 사용하면 브라우저가 사이트에 필요한 연결을 미리 예상할 수 있다.

미리 연결된 리소스는 바로 접근할 수 있게 되어 데이터를 받아올 일이 생기면 해당 연결을 사용해서 리소스만 빠르게   가져올 수 있도록 도와준다.

이 방법으로 리소스 로드 시간을 줄일 수 있다.

<link
   rel="preconnect"
   href="https://firestore.googleapis.com"
   crossorigin
/>

crossorigin은 리소스를 요청한 위치와 요청에 응답할 위치가 서로 다른 경우 CORS 요청 모드를 설정한다는 의미이다.

 

주의사항

미리 연결해 두는 만큼 CPU 사용이 늘어날 수 있고 연결을 빠르게 종료한다면 연결 작업에 대한 낭비가 일어나 유의해야 한다.

 

개선 결과

개선 전 점수와 개선 후 점수의 차이이다.

 

 

 렌더링 최적화

💬 방법 1. 스켈레톤 UI

원인

리소스가 로딩 되는 동안 아무것도 보여지지 않다가 로딩이 끝난 후 요소들이 채워지게 되면 레이아웃이 급격하게 변화하는 레이아웃 쉬프트( layout shift ) 현상이 발생하였다.

Lighthouse 캡처하여 가져와서 화질이 안 좋아요..ㅜㅜ

개선 방법

SkeletonUI를 사용한다.

SkeletonUI는 데이터가 로드될 빈 영역을 먼저 잡아놓고, 데이터가 로딩되면 데이터를 채워넣는 형식이다.

이 방법으로 로딩 중에도 컨텐츠 영역이 보일 수 있게 하여 레이아웃 쉬프트 현상을 최소화할 수 있다.

개선 결과

개선 전과 개선 후 점수의 차이이다.

 

누적 레이아웃 이동정도를 나타내는 CLP가 대폭 상승된 결과를 얻었다.

 

🔅 최종 성능 점수와 후기

최적화를 마친 후 최종적인 퍼포먼스 성능 점수는 다음과 같다.

초기 점수에 비하면 130%정도 향상된 것을 볼 수 있다.

 

 

조금 더 높은 성능으로 개선하지 못한 것에 대한 아쉬움이 남는다. 욕심은 끝이 없어...

 

이번에 처음으로 최적화에 대해 깊게 파고들어봤는데, 재밌으면서도 까다롭다는 생각이 들었다.

 

 Lighthouse로 성능 평가를 할 경우 각 지표에 따라 평가를 해주기 때문에 현 성능 상태를 직관적으로 알 수 있었다. 그리고 개발자도구의 network와 performance 탭을 함께 사용하며 어떤 부분에서 로딩이나 렌더링 문제가 발생하는 지 살펴 보았고, 그걸 토대로 최적화를 진행했더니 대부분 다 성능 향상의 결과를 보였다. 문제의 근본을 알고 해결하려 드니 좀 더 단순해지는 느낌을 받았다. 여러 도구를 통해 정확히 잘 확인하는 능력을 더 길러야겠다고 생각했다.