본문 바로가기
펀잇

펀잇을 성능 개선해보자

by 해-온 2023. 10. 15.

 

 

펀잇의 light house 점수이다.

3G Fast 모바일 환경 기준 46점에 LCP 11초, 충격적인 결과이다.

 

펀잇의 경우 초기 메인 화면에 api 통신이 많고, 이미지가 많아 로딩에 많은 속도가 걸린다.

특히 약 7000개의 데이터를 불러오는 상품 목록의 경우 무한 스크롤을 이용하고 있는데 이미지가 늦게 떠 사용성이 떨어진다.

이를 개선해 보도록 하자.

 

 

성능 관리 대상 설정

성능 개선에 있어 기능적으로 가장 핵심이 되는 요구사항을 정해보자.

 

  • 리뷰 및 꿀조합 작성 (이미지 업로드 부분)
  • 이미지 로딩

크게 두 가지로 정의할 수 있다.

가장 큰 카테고리로보자면 '이미지'이다.

 

 

성능 예산 세우기

어떤 지표들(metrics)을 사용하기로 했고, 선정한 이유

  • 정량 기반 지표 (quantity based metrics)
    • 카테고리 api 요청 캐싱: 카테고리 api를 홈화면에서 계속 불러오는데 변할 가능성이 거의 없어서 캐싱을 하기로 했다.
  • 시간 기반 지표 (timing based metrics / milestone metrics)
    • TBT: 사진을 폼에 업로드 할 때 걸리는 시간이 오래 걸려 이를 축소시키고자 했다.
    • LCP: 우리 서비스의 LCP는 이미지이다. 따라서 이미지 최적화를 하면 LCP가 줄어들 것이라고 예상한다.
    • CLS: 네트워크 상황이 좋지 않을 때 Layout Shift가 일어나면 버벅거릴 수 있다.
  • 규칙 기반 지표 (rule based metrics)
    • Lighthouse: 사용자가 없어도 분석할 수 있고, 일관된 환경에서 페이지를 빠르게 분석할 수 있다.

 

측정 환경

  • device: 모바일
  • Network throttling speed: 3G Fast

 

우리 서비스의 성능 예산

  • lighthouse 85점 이상
  • 리뷰 업로드 페이지의 TBT(Total Blocking Time) 300ms 미만
  • CLS 0.1 미만

 

개선포인트

이제 이 예산들을 바탕으로 개선 포인트를 잡아보자.

 

🪄 메인 UI 개선

현재 메인 페이지에서 불러오는 사진의 개수가 많고 용량이 크다.

접속 시 약 10MB 이상의 resources를 가져온다.

메인 UI를 바꿔 화면에서 불러오는 사진의 개수와 용량을 줄일 수 있다.

 

🪄이미지 개선

이미지의 업로드 속도를 향상시킨다.

 

리뷰를 작성하면서 이미지를 업로드할 때  browser-image-compression 라이브러리를 통해서 압축을 하고 있었다.

하지만 압축하는 시간이 오래 걸려 사용성이 떨어지는 부작용이 있었다.

(모바일 3G 환경에서 2MB 용량의 이미지를 업로드하는데 10초 이상 소요)

 

AWS의 람다를 적용해 볼까 했지만 비용 문제로 반려되었고,

form을 제출하는 시점에 압축을 진행하려 했으나 이도 조삼모사인 느낌이라 반려되었다.

결국 아래와 같은 방법을 직접 적용하기로 하였다.

  • 이미지의 확장자를 png/jpeg에서 용량이 2~30% 적은 webp로 변환한다.
  • 이미지 크기를 조절한다.

🪄Code Splitting

번들 사이즈를 줄이고 code splitting을 한다.

현재 기준 447KB이다.

 

🪄 CLS 개선

현재 배너 이미지에 Layout Shift가 일어난다.

또, 따로 스켈레톤이나 로딩 애니메이션이 없어 사용자가 로딩 중인 상태인지 인지하기 어렵다.

  • 로딩 UI를 구현한다.
  • 메인 페이지의 배너 이미지에 width와 height 속성을 추가한다.
  • 카테고리 이미지를 압축한다.

🪄 폰트

Pretendard를 사용하고 있는데 당장 사용하지 않는 폰트까지 모두 들고 오고 있다.

약 2.5MB 정도 차지하고 있어 성능을 저하한다.

  • 폰트를 css에서 link로 가져오도록 변경한다.
  • preload를 설정한다.

🪄 API 캐싱

카테고리는 총 2가지, '간편 식사/과자류' 등 상품 종류와 'CU/GS25' 등 편의점 종류로 나누어져 있다.

카테고리를 나누어 재요청을 보내지 않도록 한다.

 

🪄 S3 정적 파일 serving

  • Cloudfront를 사용해 이미지를 캐싱한다. 웹 리소스를 압축하고 캐싱한다.
  • React 빌드 파일을 S3에 업로드하고 정적 파일을 서빙한다.
  • 이미지 파일을 S3에 업로드하고 정적 파일을 서빙한다.

 

개선한 것

✨ 메인 UI 개선

메인 UI를 개선하여 정적인 이미지를 받아오게 해 api 요청을 최소화했다.

이를 통해 초기 데이터를 가져오는 양을 10MB에서 약 7MB로 줄였다.

 

<기존 UI>

<바뀐 UI>

 

✨ 이미지 개선

리뷰 작성 시 이미지는 원본 그대로 받기로 결정하였다.

S3에서 사진을 다운로드한 후 imagemin-cli 툴을 사용해 압축과 webp 변환을 진행하였다.

 

총용량을 156MB에서 22MB로 줄이고

145개의 이미지의 확장자를 webp로 변경하였다.

또한, 이미지의 크기도 줄여 용량을 줄였다.

 

Code splitting

lazy와 suspense를 사용해 번들링을 진행하였다.

447KB에서 374KB로 줄였다.

 

가장 효과를 많이 본 것은 개발 환경일 때의 번들링 용량인데

약 2MB 이상의 용량을 442KB로 줄일 수 있었다.

 

<개선 전>

 

<개선 후>

 

CLS 개선

로딩을 구현해 콘텐츠 로딩 전 후의 간극을 없앴다.

로딩 화면과 스켈레톤을 사용해 사용자가 로딩 상태임을 인식할 수 있도록 하였다.

 

또, LCP가 일어나는 배너 이미지에 높이 값을 추가했다.

CLS가 아래와 같이 감소한 것을 볼 수 있다.

 

폰트

디자인 시스템에서 폰트를 가져와 적용하고 있었다.

이를 삭제하고 펀잇 본 프로젝트 html에서 link로 넣었다.

 

    <link
      rel="preload"
      as="font"
      crossorigin
      href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.8/dist/web/variable/pretendardvariable-dynamic-subset.css"
      onload="this.onload=null; this.rel='stylesheet'"
    />

 

또한 위와 같이 FOUT 현상이 발생하고 있었다.

이를 preload를 사용해 미리 폰트를 가져왔고 FOUT 현상을 방지하였다.

 

2.5MB에 해당하는 Pretendard 폰트의 용량을 425KB로 줄였다.

가변 다이내믹 세그먼트를 사용했고, 기존 서브셋보다 현저히 적은 용량으로 사용할 수 있었다.

 

 

API 캐싱

카테고리가 2가지로 나누어져 있기 때문에 카테고리 값이 바뀔 때마다 요청이 일어난다.

카테고리 데이터는 변하지 않기 때문에 음식과 상점 카테고리로 나누고 stale time을 Infinity로 주었다.

 

 

따라서 카테고리 목록은 첫 번째 요청 후 재요청을 하지 않는다.

 

S3 정적 파일 serving

직접 압축 등 개선을 거친 이미지를 S3에 다시 업로드하고, Cloudfront로 CDN을 적용한다.

max-age=31536000으로 설정해 브라우저에 이미지를 캐싱한다.

 

웹 리소스 캐싱의 경우 아래와 같이 설정해 주었다.

  • HTML 리소스: s-maxage=31536000, max-age=0 설정
  • JS 및 기타 리소스: 해시를 추가해 max-age=31536000 설정

HTML 파일은 배포가 이루어질 때마다 값이 바뀔 수 있다.

따라서 브라우저는 항상 새로운 배포가 있는지 확인해야 한다.

(여기서 s-maxage는 중간 서버에서만 적용되는 max-age 값을 설정하기 위해 사용할 수 있다.

 

s-maxage=31536000, max-age=0로 설정 시 CDN에서는 1년간 캐시 되고, 브라우저에서는 매번 재검증 요청을 보낸다)

 

JS 및 기타 리소스의 경우 chunk에 hash를 추가해 캐시 버스팅을 적용하였다.

 

374KB였던 용량이 120KB로 줄어든 것을 볼 수 있다.

 

<개선 전>

 

<개선 후>

 

성능 개선 후 결과

 

  • lighthouse 85점 이상
  • 리뷰 업로드 페이지의 TBT(Total Blocking Time) 300ms 미만
  • CLS 0.1 미만

모든 성능 예산을 통과했음을 알 수 있다.

 

또한, 초기에 불러오는 리소스는 약 10MB였는데 최종 결과 2.9MB로 줄어든 것을 볼 수 있다.

 

 

 

미션에서 성능 최적화 공부를 할 때 너무 재미있어서 밤을 새우고 아침 7시에 잔 적이 있었다.

이때 배운 내용을 직접 우리 프로젝트에 적용해 볼 수 있어서 더 뜻깊었다.

수치가 눈에 보이고 개선이 체감되니까 정말 재미있었다. 

특히 팀원의 휴대폰이 3G여서 성능 개선의 효과를 눈에 띄게 실감할 수 있었다.

이래서 최적화를 하는구나...🥹

댓글