FE 성능 최적화 (로딩) - woowacourse-teams/2021-gpu-is-mine GitHub Wiki

1. 성능 분석

시작 페이지 /member/login

webpagetest.org

www webpagetest org_result_210906_BiDcQ2_afdfa4ea4f7ef56e5f084038aeba3477_1_details_

  • LCP 3.x로 길다: why?
  • fonts.googleapi.com - css2 접속이 끝나야 document completed 가 된다 : fonts.googleapi.com 이 render-blocking resource 인 것 같다
  • NotoSans Font 도입의 목적이 다르다. 디자인의 일관성을 위해서가 아니라, 브라우저에서 한국어가 지원되지 않은 경우에 대비해서 보내주는 것.
  • Waterfall View 의 단어의 의미를 명확하게 이해(Start Render, Document Complete 등)
lighthouse

_home_bigsaigon333_Downloads_dev gpuismine com-20210906T140358 html

개선방안

  • cloudfront origin sheild 설정 => TTFB의 시간 줄이기
  • non-blocking render 리소스 형태로 폰트 다운 변환
  • main.bundle.js의 번들 크기 줄이기
  • main.bundle.jscahce-control 헤더 추가를 통한 캐싱 활용

2. 개선

1. cloudfront origin sheild 설정 => TTFB의 시간 줄이기

Screenshot_2021-09-06_16-10-01

Origin Shield란 additional cache layer입니다.

Without Origin Shield With Origin Shield
With Origin Shield With Origin Shield

=> 눈에 띌만큼 LCP가 줄어들지는 아니함

affected Origin Shield

Origin Shiled 적용 후 WebPageTest Detail

2. Response Header에 cache-control 추가

  1. main.bundle.jscache-control 헤더 추가를 통한 캐싱 활용
  • main.bundle.js: cache-control: public, max-age=31536000, immutable image

main.bundle.js의 내용이 변경될 경우, html에서 최신의 main.bundle.js 를 불러오게 하기 위하여, 빌드시에 main.bundle.js 파일명에 [fullhash]를 추가하였습니다. main.[fullhash].js

  1. index.htmlcache-control 헤더 추가를 통한 캐싱 활용
  1. Cache-control 헤더 추가 한 뒤의 webpagetest.org 결과
  • LCP의 큰 차이가 없다: cache-control 설정을 하나도 안해두어도, 브라우져가 자체적으로 캐싱을 하기 때문에, 속도면에서는 큰 차이가 없는 것으로 생각된다. 다만, lighthouse로 검사시에는 정적 자산에 대한 max-age 설정을 보기 때문에 의미가 있다고 생각됨
  • Repeat View 의 LCP 가 증가하였다: index.html 은 브라우져에서 캐시를 하지 않고 항상 캐시 서버(CloudFront)에서 검증을 하게 하였기 때문이다.

https://www.webpagetest.org/result/210907_AiDc2V_eca9d8e32ec8d8f5125558f3ba40dc01/

after-cache-control

3. font 를 non-render-blocking resource로 변경

  1. 기본적으로 CSS는 render blocking resource이다. By default, CSS is treated as a render blocking resource.
  1. head 태그 내의 inline style 태그도 당연히 render-blocking resource이다.
  1. inline style 태그내에 @import 가 있다면 파일 다운로드는 비동기적으로 수행되지만, 이를 다 다운받을 때까지 render는 blocking 된다.

Q1. Link 태그라서 render blocking 인가? stylesheet 라서 render blocking 인가? -> stylesheet인거 같은데..? 근거 필요함 근거:

Q3. link rel="preload" as="style" href="...css" 는 왜 non render blocking 이 되는것인가? preload는 그냥 link 보다 더 우선순위가 높아서 반드시 가져와야 한다. 그런데 비동기로 가져오는 것인가????

  1. webpageTest 결과 LCP 0.8s 감소

https://www.webpagetest.org/result/210907_BiDc7Y_91406eb153b07e7b75a97c6bc809e488/ preload-link

3. Code Splitting을 통한 JS 번들 사이즈 최적화

  1. chart.js 가 차지하는 번들 사이즈가 크다 -> chart.js 를 별도의 청크로 분리하고, 이를 동적으로 import 한다.

=> Chart 컴포넌트를 별도의 청크로 분리한 후 React.lazy, React.Suspense 를 이용하여 동적으로 import 한다

after-suspense
  1. History.js 가 차지하는 번들 사이즈가 크다. (28.05Kb)
  • 현재 History.js 는 테스트 파일에서만 사용하고 있는데, 번들에도 포함되고 있다. react-router-dom 에서 사용하기 때문에 번들에 포함되는지 확인이 필요하다.

-> History.js 를 삭제한 후 번들링을 해본 결과, Type 때문에 제대로 되지 않는다. [email protected] 내부에서 history.js 에 대한 의존성을 가지고 있어서, history.js 를 devDependencies 에 넣는 것과 무관하게 번들에 포함되게 되어있다. https://bundlephobia.com/package/[email protected]

[email protected] 에서는 history.js 에 대한 의존성이 없어 번들 사이즈가 줄어들 수 있다. https://bundlephobia.com/package/[email protected]

https://reacttraining.com/blog/react-router-v6-pre/

하지만 history.js가 전체 번들에서 차지하는 사이즈가 28.05Kb로 유의미하게 크지 않으므로 일단 PASS)

  1. 외부 패키지를 별도 청크로 분리한다. react, react-dom, styled-components 등의 외부 패키지를 별도 청크로 분리한다. webpack의 optimization.splitChunks로 대응한다. default 옵션을 사용하되, chunk대상만 all로 수정한다 https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks

=> 최종 결과 LCP 1.78s 로 감소

https://www.webpagetest.org/result/210908_BiDc9E_4950f7342431ea5450af9e02e9854b48/1/details/

4. BUGFIX: font preload 후 onload 때 rel 을 stylesheet로 바꾸어야 한다.

preload 는 단순히 network 요청을 빨리 하는 것이고, load되었을 때 할 작업을 명시해줘야 한다. 따라서 stylesheet 로 속성을 변경해주지 않으면 css로서의 파싱, 실행이 되지 않는다.

우리가 고민했던 문제들

HTTP request 캐싱 관련 의문점

  • main.bundle.js 요청 헤더에 no-cache 옵션이 존재하는데, http 요청없이 브라우저 캐시(memory/disk)로 부터 데이터를 가져옴(TTFB = 0)
  • 크롬 개발자 도구에 Disable cache 옵션을 줄 경우 모든 요청 헤더에 no-cache가 포함됨.
  • http 요청 타겟을 js가 아닌 index.html에 주목을 했어야함. (in chrome)
    1. Disable cache: ON => 캐싱 X
      • 요청 헤더 cache-control: no-cache
      • 최초 요청에 대한 html 응답 200 / js 응답 200
      • 재요청에 대한 응답 200 / js 응답 200
    2. Disable cache: OFF => 캐싱 O
      • 요청 헤더 cache-control: max-age=0
      • 최초 요청에 대한 html 응답 200 / js 응답 200
      • 재요청에 대한 html 응답 304(network) / js 응답 200 (in memory)
  • 브라우저 캐시로 부터 받은 응답은 실제 요청에 대한 실제 응답인가?chrome vs firefox
    1. chrome + Disable cache X: js의 응답 date값은 항상 동일 => 기존 응답
    2. firefox + Disable cache X: js의 응답 date가 항상 다른 값 => 실제 응답
  • 참고자료

react-is

  • react-is 가 왜 dependencies 에 포함되어 있는가? yarn berry 설치시 이슈가 있었다.
  • react-is 는 styled-components의 peerDependencies 이고, peerDependencies는 설치시에 부모(사용하는 측)에서 dependencies를 주입해줘야 한다.(설치해야한다) yarn classic을 쓸 때는 별다른 일 없이 잘 사용하고 있었다. (아마도 유령 hoisiting 에 의하여 사용가능했던 것이 아닐까...) 그러나 yarn berry의 엄격한 의존성 정책으로 인해 명시적으로 dependencies에 추가해주어야 했다.
  • https://github.com/yarnpkg/berry/issues/966

용어 정리

WebPageTest

  1. Start Render: 화면에 그려진 픽셀을 실제로 볼 수 있는 시점
  2. Document Complete: onload 이벤트 + 정적 이미지 로딩 완료

해결되지 않은 궁금증

  1. 크롬 개발자 도구 Disable cache ON -> OFF : main.bundle.js파일에 항상 request 헤더에 no-cache 옵션이 남아있음.

요약 정리

https://rattle-king-c48.notion.site/GPU-IS-MINE-089cf77a60764695972a6644f1fb1194

참고

https://nooshu.com/blog/2019/10/02/how-to-read-a-wpt-waterfall-chart/

⚠️ **GitHub.com Fallback** ⚠️