sharingStorage

성능개선기1 - 올바른 이미지 포멧사용의 중요성 & code splitting 본문

Front-End/성능개선

성능개선기1 - 올바른 이미지 포멧사용의 중요성 & code splitting

Anstrengung 2024. 10. 3. 18:40

들어가며

이전 시간엔 web성능에서 중요한 지표인 core web vitals와 성능을 확인할 수 있는 도구인 Lighthouse를 알아보았으니 이를 통해 현재 서비스중인 mile의 성능을 확인하고 개선해보려고 합니다.

가장 먼저 Lighthouse로 현재 mile의 성능을 측정해본 결과 main페이지부터 아래와 같은 엄청나게 성능이 좋지 않다는 것을 확인했고 vite-bundle-visualizer 와 같은 도구로 번들사이즈를 확인하며 데모데이를 향해 달려가면서 놓친 부분과 크고 작은 실수들을 바로 잡으면서 성능을 개선해보겠습니다.

성능 확인과 번들사이즈 확인

아래와 같이 Lighthouse로 엄청나게 좋지 않은 메인페이지의 성능을 확인하였고 이와 관련해서 vite-bundle-visualizer를 사용하여 번들 사이즈도 확인해보았습니다.

 

 

Lighthouse 성능지표

처참하죠??

이전 글에서 말씀드렸다 싶히 FCP는 1.8초 이하여야하고 LCP 2.5초이하여야 양호 수준이지만 이를 아득히 뛰어넘고 있습니다. 이러한 문제의 원인을 파악하고 FCP와 LCP의 개선을 위주로해서 성능지표를 개선해보겠습니다.

 

번들 (Bundle) 이란?

일반적으로 서로 참조 관계에 있는 파일들을 모아서 하나의 파일로 묶은 것을 bundle이라고 하며

번들링은 JavaScript 및 CSS 파일을 하나의 파일로 결합하는 것을 의미합니다.

 

vite-bundle-visualizer 를 통한 번들 사이즈 확인

vite-bundle-visualizer는 vite 빌드시 생성된 번들의 시각적인 표현을 제공합니다. 이를 사용하여 번들링된 Javascript 파일의 크기, 의존성 그래프, 각 모듈의 상호작용 등을 시각적으로 확인하여 번들 구조를 이해하고 최적화할 수 있도록 도와줍니다. 번들이 너무 크거나 불필요한 의존성이 많은 경우 이는 성능 저하의 원인이 될 수 있습니다.

아래는 mile 서비스의 번들 크기를 확인한 결과입니다.

build 파일의 크기
vite-bundle-visualizer 확인

사진을 자세히보면 그 어떤 라이브러리보다 assets폴더 안에 svg파일이 큰것을 볼 수 있는데요 이는 이미지를 사용할 때 이미지 포멧을 잘못사용하고 있기 때문입니다.

 

번들사이즈를 최소화하는 방법

  1. 코드 최적화
  2. 불필요한 의존성 제거
  3. code splitting( 지연로딩 (필요한 코드만 로드하도록 분할))
  4. 트리 쉐이킹 (사용하지 않는 코드 제거)

위 와 같은 방법으로 번들사이즈를 최소화할 수 있습니다. 일단 가장 직관적으로 확인할 수 있는 문제부터 해결해나가보겠습니다.

 

개선점 1 - 잘못된 image 확장자 사용

저희 서비스에서는 대부분의 이미지를 svg로 사용하고 있었고 일러스트와 복잡한 이미지에서 svg는 엄청난 사이즈를 자랑하고 있는 것을 직접 확인하였습니다.

기본적으로 svg가 웹 사이트 로딩속도가 빠르고 이미지가 왜곡되지 않으며 XML 코드이기때문에 파일크기가 작다는 정보로 svg를 사용하였고 복잡한 SVG는 비효율적이라는 것은 알았지만 어느 정도가 복잡한 것인지 정도의 차이를 몰랐기 때문에 발생한 문제였던 것 같습니다.

따라서 단순히 확장자를 변경하기 앞서 간단히 어떤 상황에서 어떤 image 포멧을 사용해야하는지 알아보고 넘어가도록 하겠습니다.

 

레스터 (Raster) 방식이란?

이미지의 모양과 색을 색상 정보가 담긴 픽셀로 표현하는 방식입니다.

흔히 비트맵이라고 불리며 자연스러운 이미지를 표현할 수 있지만 확대를 할 경우 그림이 깨져보일 수 있으며 픽셀수가 많아질 수록 파일의 크기가 커지는 단점이 존재합니다.

  • PNG, JPG, GIF 등이 레스터 방식입니다.

 

PNG

용량은 커도 화질이 좋았으면 좋겠다. (선이 선명한 그래픽, 일러스트, 투명배경 필요한 이미지)

  • 투명한 배경이 필요할 때, 비교적 좋은 화질이 필요할 때 사용합니다.
  • 무손실 압축 포맷 방식을 사용하기 때문에 압축하더라도 원본과 동일한 형태를 유지합니다.
  • 투명도(알파 채널)을 지원합니다.
  • 현실 사진보다는 단순한 이미지에 적합합니다.
  • 적합한 사용 : 선이 선명하고 평평한 색상과 명확한 테스트 구분 등의 특징을 가진 스크린샷 캡쳐본, 일러스트 등

JPG/JPEG

화질이 좋지 않아도 용량이 작았으면 좋겠다. (실제 사진)

  • 약 1600만개의 색상을 표현할 수 있습니다.
  • 손실 압축 포맷 방식을 사용하여 압축하는 동안 이미지 품질이 저하되고 이로 인해 반복적으로 저장하면 품질이 지속적으로 손실될 수 있습니다.
  • 투명 채널을 지원하지 않습니다. (누끼와 같은 기능)
  • 적합한 사용 : 색상, 그라데이션, 텍스처가 다양한 사진 등

SVG

다양한 크기에 유연하게 대응하고 싶다. (복잡하지 않은 아이콘)

  • 2차원 벡터 그래픽을 표현하기 위한 XML기반의 파일 형식
  • 레스터 방식과 달리 벡터방식(수학적 함수를 이용하여 도형과 선을 그려서 표현)을 사용하고 이미지 확대로 인한 깨짐현상이 없고 선명함을 유지합니다.
  • 색상의 자연스러운 변화나 세밀한 표현은 어려울 수 있습니다.
  • 레스터방식의 확장자에 비해 비교적 작은 용량을 가지지만 복잡한 이미지의 경우 수학적 계산이 필요한 만큼 크기가 기하급수적으로 커질 수 있습니다.
  • d3.js나 Raphael.js 같은 자바스크립트 라이브러리에서 차트나 그래프를 표현하는 방식이 바로 SVG 이미지입니다.
  • 투명도(알파 채널)을 지원합니다.

WebP (Web Picture Format)

지원하지 않는 소수의 브라우저에 대응하기만 하면 만능

  • JPG보다 용량은 70%만큼 낮지만 더 뛰어난 색상지원을 하는 포맷
  • PNG처럼 투명도(알파 채널)을 지원합니다.
  • GIF처럼 애니메이션 표현도 가능합니다.
  • picture 태그와 source태그를 활용해서 지원하지 않는 브라우저에선 fallback 이미지를 보여주는 방식으로 사용할 수 있습니다.
  • Chrome, Safari, Firefox, Edge, Opera 브라우저 및 많은 다른 도구와 소프트웨어 라이브러리에서 기본적으로 지원되지만 Safari는 미지원하는 버전도 상당히 존재하고 GPU렌더링을 지원하지 않아 해당 브라우저에 성능저하가 발생할 수 있습니다.
  • Webp는 용량이 적은 대신 화면에 렌더링하기 위해 GPU로 계산해야하는 양이 많습니다.

이러한 정보를 토대로 차례대로 개선해나가면서 성능을 확인해보려고 합니다.

우선은 webp를 제외한 적절한 이미지 포멧을 사용해보고 추후 webp로 변경해보려고 합니다.

 

직접 확인해본 SVG와 PNG

같은 일러스트 이미지여도 PNG는 단 5KB의 용량을 가지는데에 비해 svg는 복잡한 계산식으로 인해 945KB의 용량을 가지는 것을 확인할 수 있었습니다.

 

메인페이지에서 이렇게 잘못 사용된 이미지 확장자를 적절하게 변경하였고 이를 통해 FCP와 LCP의 시간을 약 두배정도 절감하였지만 아직도 기준에는 못미치는 상태입니다.

 

개선점 2 - code splitting과 lazy loading 적용하기

개발자 도구 Coverage 활용하기

chrome devtools의 Coverage탭을 활용하면 해당 페이지에서 사용되는 JS, CSS 파일을 불러옵니다. 불러온 파일들은 전체크기, Type, 사용되지 않는 크기와 비율 등의 정보를 함께 볼 수 있습니다. (빨간색: 사용되지 않는 코드, 회색: 사용중인 코드)

해당 파일을 클릭하면 내부의 현재 페이지에서 쓰이지않는 CSS, JS 코드 파일들을 확인할 수 있습니다.

coverage를 확인해본 결과 main에서 사용하지 않는 다른 페이지의 코드나 이미지파일들도 함께 불러오면서 main page의 성능을 낮추고 있다는 것을 확인하였고 main페이지를 제외한 다른 route 파일들은 code splitting을 적용하겠습니다.

code splitting (코드 분할)

code splitting이란 번들링을 통해 커진 번들이 거대해지는 것을 방지하기 위해 말그대로 코드를 나누는 것입니다. 정확히는 런타임에 번들을 동적으로 만들고 불러오면서 “지연 로딩(lazy loading)”을 통해 앱의 성능을 향상시킵니다.

이는 코드 양을 줄이지 않고도 필요하지 않은 코드를 불러오지 않게 하여 앱의 초기 로딩에 필요한 비용을 줄여줍니다.

React에서 코드 분할을 적용하는 가장 간단한 시작점은 route level에서의 분할입니다.

가장 간단하면서도 직관적이게 우리의 애플리케이션에서 code splitting을 적용할 수 있습니다.

또한 버튼 클릭과같은 상호작용시에만 렌더링 되는 큰 컴포넌트나 화면에 보이지 않는(뷰포트에 나타나지 않는) 다른 요소를 분리하는 것도 고려할 수 있습니다.

lazy loading

react에서는 lazy를 통해 컴포넌트 코드의 로딩을 연기할 수 있습니다.

lazy는 return 값으로 React component를 반환하고 이는 tree에 렌더링할 수 있습니다.

const Main = lazy(() => import('../pages/main/Main'));
const PostDetail = lazy(() => import('../pages/postDetail/PostDetail'));
const GroupFeed = lazy(() => import('../pages/groupFeed/GroupFeed'));

lazy loading을 사용하기 위해선 위 컴포넌트들이 default export로 export되어있어야 합니다.

export default Main;

또한 lazy component가 로딩되는 동안 보여져야할 컴포넌트가 필요하고 이 때 Suspense의 fallback을 사용하여 loading spinner와 같은 로딩표시기를 보여줄 수 있습니다.

    <BrowserRouter>
      <ScrollTotop />
      <Suspense fallback={<Loading />}>
        <Routes>
          <Route path="/" element={<Main />} />
          <Route path="/group/:groupId" element={<GroupFeed />} />
          <Route path="/detail/:groupId/:postId" element={<PostDetail />} />
          (...중략...)

이와 같은 처리 후 네트워크 탭을 확인해보면 이전에 main page에서 사용하지 않는 CreateGroup, GroupFeed component 등을 불러오던 네트워크 통신이 사라지고 Main.tsx 하나만 불러오는 것을 확인할 수 있습니다.

모든 페이지 컴포넌트를 불러오는 지연 로딩 적용 전 main page network탭
main 페이지에서 필요하지 않은 부분에 lazy loading을 적용한 후 network 탭

 

이러한 작업을 통해 FCP와 LCP가 각각 8.1초→ 4.6초, 17.3초→ 11.2초로 개선된 것을 확인할 수 있습니다.

 

모든 페이지에서 사용하는 이미지 포멧 변경

 

Coverage 탭을 보면 메인페이지에서 사용하지 않는 svgs파일들도 많은 용량을 차지하며 성능에 악영향을 주고있는 것을 확인하였습니다.

해서 현재 페이지에서 사용하지 않는 이미지 파일도 적절한 확장자를 사용하고, 사용하지 않는 파일들을 정리해준 후 Lighthouse의 성능지표를 확인해보았습니다.

 

결과적으로 FCP와 LCP 측정값이 눈에 띄게 개선되었고 FCP는 구글 웹페이지 성능 지표 “양호”기준에 많이 가까워졌습니다.

 

마지막으로 vite-bundle-visualizer를 확인해보면 기존에 하나의 큰 번들링된 파일이 아닌 각각의 페이지로 code splitting이 적용된 것을 확인해볼 수 있습니다.

 

마치며

올바르지 않은 이미지 형식 포멧 사용이 이렇게 까지 크게 성능에 영향을 미친다는 것을 확인해보며 그동안 잘못 사용하고 있는 부분들을 수정해보았고, lazy loading과 code spliting을 적용하여 성능이 개선되는 과정을 확인해보았습니다.

 

다음시간에는 Lighthouse의 Dignostics를 사용해서 성능개선지점을 파악하고 이를 개선해보겠습니다.

이미지확장자를 webp로 변경한 후 성능변화를 확인해보고 과 rendering blocking을 개선해보는 작업, css minify, 더 적은 용량의 라이브러리를 도입하고 찾아보는 작업을 통해 성능을 개선해본 작업을 공유해보도록 하겠습니다.

 

 

Reference

https://developer.mozilla.org/en-US/docs/Learn/Performance/Perceived_performance

https://web.dev/articles/code-splitting-suspense

https://react.dev/reference/react/lazy

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import

https://developers.google.com/speed/webp?hl=ko

 

'Front-End > 성능개선' 카테고리의 다른 글

성능개선 (core-web-vitals & Lighthouse)  (2) 2024.09.24
Comments