프론트엔드의 성능 최적화
성능 개선 이유
성능은 고객 유치와 SEO에 밀접한 관계가 있습니다. 사이트 속도가 빨라지면 사용자 경험(UX)가 개선되며, 검색 엔진의 순위에도 긍정적인 영향을 줍니다.
성능 개선에 따른 효과
- Tokopedia: 3G 연결에서 렌더링 시간을 14초에서 2초로 줄여 방문자 19% 증가
- Pinterest: 페이지 성능을 개선해 대기 시간을 40% 줄이고 SEO 트래픽과 전환율이 각각 15% 증가
성능 측정 방식
성능 측정 환경
- 측정 컴퓨터: 바다 (윈도우 11)
- 측정 대상 기기 : pc
- 측정 대상 모드 : dev 배포 페이지
- 측정 브라우저 : 크롬 브라우저 (시크릿 모드)
- 측정 전 : 브라우저 캐시를 지우고 시작한다.
성능 측정 대상 및 예산
성능 측정 대상
리뷰미 서비스의 핵심 기능인 리뷰 작성이 있는 리뷰 작성 페이지를 우선으로 성능 개선(리소스 경량화, 애니메이션 최적화)을 하고, 그외의 페이지는 리소스를 경량화하는 작업을 진행했습니다.
항목 | 리뷰 작성 페이지 | 홈 페이지 | 리뷰 연결 페이지 | 그 외 페이지 |
---|---|---|---|---|
JS 최적화 | O | O | O | O |
CSS 최적화 | O | O | O | O |
이미지 최적화 | O | O | O | O |
캐러셀 최적화 | O | O | x | x |
Lighthouse 측정 대상 | O | O | O | x |
성능 예산
- 구글의 RAIL 모델 가이드 라인 참고
- 지표 (프론트 개인 미션 성능 개선 항목을 우선 따른다)
- 소스 파일 용량 : 정량 기반 지표 사용
- LCP ,FCP : 수치 기반 지표 - 빠름 기준 (LCP: 2.5s, FCP:1.8s)
성능 측정 도구
- Lighthouse : 전반적인 성능 측정
- Chrome devtools: 네트워크,CPU 성능에 따른 Layout shift, Frame drop 측정
- Webpack bundle analyzer : 번들링된 리소스 크기 확인
- WebPageTest : 서울 빠른 3G에서 두 번 요청 시 LCP 측정
성능 최적화
캐시 사용
CloudFront에서 Managed-CachingOptimized
캐시 정책을 사용하고 있습니다. Managed-CachingOptimized
캐시 정책을 사용하면 CloudFront는 기본 TTL(하루)을 설정하고 브라우저가 캐시를 받을 수 있도록 합니다. 브라우저는 CloudFront에서 설정된 캐시 정책에 따라 파일을 캐시하며, 기본적으로 하루 동안 캐시된 콘텐츠를 사용합니다. 캐시 정책관련 프론트 회의에서 현재 변경 사항이 많은 단계이기 때문에, 캐시 시간을 더 늘이기 보다는 현재 사용하고 있는 CloudFront에서 제공하는 기본 TTL 시간인 하루를 유지하기로 했습니다. 그래서
추가적인 캐시 작업은 진행하지 않았습니다.
JS 최적화
CSS-in-JS인 emotion
을 사용 중이라 CSS 최적화를 진행하지 않습니다. 스타일 코드도 JS이기 때문에, 최대한 JS를 압축하고 필요한 리소스만 부르는 방향으로 최적화를 진행했습니다.
1차 JS 최적화 시도
Webpack 의 production 모드로 빌드 시, Webpack에서 JS 최적화를 해줍니다. 이를 기본적으로 사용하되 추가적인 최적화를 위해 아래의 것들을 시도했습니다.
- TerserPlugin 설정을 통한 불필요한 콘솔, 디버깅 등 코드 삭제
- 동적 import와
chunk: 'all'
옵션을 사용한 Code Splitting
그 결과 작성 페이지에서 부르는 JS 리소스 크기가 629KB에서 167KB로 줄었습니 다.
2차 JS 최적화:Brotli 압축
1차 최적화 시도에서는 CloudFront에서 자동으로 제공하는 모듈 압축 방식을 사용했습니다. CloudFront에서 클라이언트 Accept-Encoding 헤더에 따라 압축을 하는 것보다, S3에 압축된 파일을 올리면 리소스 로딩 시 압축 시간이 제외되니 로딩 속도가 더 빠릅니다.
Gzip이 아닌 Brotli를 사용한 이유는 Brotli는 Gzip에 비해 압축률이 20-25%더 높고 압축해제도 더 빠르며, 리뷰미 서비스 대상 브라우저에서 다 지원하고 있습니다. 그렇지만 혹시 있을 수 있는 Brotli 미지원 브라우저 사용자를 위해, 압축하지 않은 리소스도 S3에 올려서 사용자의 브라우저의 요청에 따라 압축된 리소스를 로드할 수 있도록 했습니다.
Brotli로 인한 개선 사항
- 작성 페이지에서 JS 리소스 크기 107KB로 감소
- Brotli 압축 시 터미널 창
작성 페이지 기준 JS 최적화 전 후 비교 (네트워크 No throttling, 캐시 사용 중지)
- JS 리소스 크기: 629KB ->107KB
- DOMContentLoaded: 1.60s -> 419ms
- Load: 1.65s -> 1.16s
이미지 최적화
SVG를 사용한 이유
리뷰미 프론트 팀은 SVG 이미지 포맷만을 사용하고 있습니다. SVG는 다양한 화면 크기에서도 동일한 품질을 지원하기 때문에, 웹과 모바일 브라우저 모두 지원하는 리뷰미 서비스에 적합한 포맷이라고 생각합니다.
SVG 최적화
svgo
플러그인을 사용해, 빌드 시 SVG 최적화를 진행했습니다.
최적화를 진행한 결과 상위 SVG 5개 파일 크기 합이 2367KB에서 1736KB로 줄었습니다.
svgo로 최적화를 진행한 후, Brotli로 압축하는 과정을 거쳐 한 번 더 SVG 파일 크기를 줄였습니다.
홈 페이지 기준 이미지 최적화 전 후 비교(1024px, 네트워크 No throttling, 캐 시 사용 중지)
- 리소스 크기: 8.7MB -> 1.4MB
- Finish: 6.47s -> 5.95s
- DOMContentLoaded: 1.79s -> 209ms
- Load: 2.30s -> 727ms
폰트 최적화
리뷰미에서 프리텐다드를 메인 폰트로 사용하고 서브 폰트로 구글 폰트인 Noto Sans를 사용하고 있습니다.
구글 폰트에는 preload
를 사용해 해당 폰트에 대한 로딩 속도를 개선했습니다.
프리텐다드 폰트에 preload
와 가변 다이나믹 서브셋(Variable Dynamic Subsetting)을 사용해 최적화를 진행했습니다.
가변 다이나믹 서브셋을 사용한 이유
다이나믹 서브셋과 가변 다이나믹 서브셋?
-
다이나믹 서브셋
- 웹 페이지에서 사용되는 글리프만 포함된 폰트 파일을 동적으로 생성하여 용량을 줄이는 방식입니다.
-
가변 다이나믹 서브셋
- 다이나믹 서브셋과 가변 폰트를 결합하여, 하나의 폰트 파일에 다양한 스타일(굵기, 너비 등)을 포함하면서 필요한 글리프만 동적으로 불러오는 방식입니다.
리뷰미 페이지에서 다양한 굵기의 프리텐다드 폰트를 사용하고 있어서, preload를 사용할 수 있으면서도 다양한 굵기의 폰트를 더 가벼운 용량으로 부르는 가변 다이나믹 서브셋을 사용하기로 했습니다.
preload가 적용된 가변 다이나믹 서브셋 적용으로 인한 최적화
개선 항목 | 개선 전 | 개선 후 |
---|---|---|
폰트 총 리소스 용량 | 2.4 MB | 209 KB |
DOMContentLoaded | 1.60s | 216ms |
Load | 1.60s | 744ms |
Waterfall에서 로딩 속도 |
캐러셀 애니메이션 최적화 및 불필요한 렌더링 개선
개선 전 네트워크, CPU에 따른 Frame drop, Layout shift
캐러셀 | 네트워크:제한 없음 , CPU: 제한 없음 | 네트워크: 3G ,CPU : X 6(감속) |
---|---|---|
작성 페이지 캐러셀 Frame drop | 있음 | 있음 (체감상으로도 버벅임이 느껴짐) |
작성 페이지 캐러셀 Layout shift | 없음 | 없음 |
홈 페이지 캐러셀 Frame drop | 캐러셀의 카드가 이동할때 마다 Frame drop 일어남 | 캐러셀의 카드가 이동할때 마다 Frame drop 일어남 |
홈 페이지 캐러셀 Layout shift | 없음 | 없음 |
애니메이션 최적화 속성 사용
window.requestAnimationFrame
, will-change
, translate3d
를 사용해 홈 페이지와 리뷰 작성 페이지에서 사용하는 캐러셀에 대한 애니메이션 최적화를 진행했습니다.
GPU는 CPU와 달리 그랙픽에 특화되어 있으며 여러 작업을 병렬처리해, 애니메이션 시 GPU를 사용하는 것이 좋습니다. translate3d
는 GPU 가속을 사용하고 will-change
는 브라우저에게 특정 요소에서 곧 발생할 가능성이 있는 변화를 미리 알려줌으로써, 해당 변화를 최적화하도록 도움을 줍니다.
window.requestAnimationFrame
브라우저의 화면 업데이트 주기(초당 60번)에 맞춰 콜백 함수를 실행해 애니메이션이 부드럽게 보이도록 합니다. 캐러셀을 작동하는 콜백 함수는 window.requestAnimationFrame
에 넣어, 보다 부드럽게 캐러셀 속 카드나 이미지들이 넘어가도록 했습니다.
메모이제이션 사용
리뷰 작성 페이지에서 사용자가 이전/다음 버튼이나 프로그레스 바를 통해 이전에 렌더링한 질문지를 다시 부를 수 있습니다. 그래서 React.memo
를 사용해 이미 한번 렌더링한 질문지를 새로 렌더링하지 않도록 했습니다.
작성 페이지(네트워크: 3G ,CPU : X 6(감속)) 캐러셀 Frame Drop 개선 전 후 비교
Frame | 개선 전 | 개선 후 |
---|---|---|
이미지 |
성능 최적화 개선 전 후 비교
홈 페이지
페이지 | Lighthouse 성능 점수 | LCP | FCP | 서울 빠른 3G(webpage test) 에서 두 번 요청 시 LCP |
---|---|---|---|---|
개선 전 | 82 | 3.3s | 0.6s | 7.215s |
개선 후 | 98 | 1.0s | 0.6s | 1.84s |
리뷰 작성 페이지
페이지 | Lighthouse 성능 점수 | LCP | FCP |
---|---|---|---|
개선 전 | 73 | 3.3s | 1.7s |
개선 후 | 95 | 1.3s | 0.7s |
리뷰 연결 페이지
페이지 | Lighthouse 성능 점수 | LCP | FCP |
---|---|---|---|
개선 전 | 85 | 2.7s | 0.7s |
개선 후 | 96 | 1.4s | 0.5s |