솔미는 성장중
프론트엔드 웹 성능 최적화 본문
성능 측정 방법
lighthouse , speedtest
cf) 모바일로 돌리면 lighthouse 내부 성능이 안좋다. -> 모바일 80점 === 데스크탑 100점
Core web vitals
면접 질문 대비하자! (fcp, Icp, tti, cls, (inp))
측정항목들을 통칭하는 말
1. FCP (First Contentful Paint)
웹페이지에서 가장 먼저 보이는 페인트가 몇 초 걸리는지.
2. LCP (Largest Contentful Paint)
웹페이지에서 가장 큰 리소스가 불러와지는 시간
거의 이미지 최적화와 유사함.
(ex. 동영상, 이미지..)
3. TBT (Total Blocking Time)
= TTI(time to interaction) - FCP(first contentful paint)
= 상호작용이 가능한 시간 - FCP
3. CLS (Currulative Layout Shift)
레이아웃 쉬프트 측정
(width, height를 잡아줘야함)
4. INP -> 이거 최적화 하는 방법도 알아두기
다음 페인트와의 상호작용
최적화 방법
로딩 최적화 / 렌더링 최적화
로딩 최적화 = core web vital과 연관된 사항
렌더링 최적화 = cls와 연관된 사항. 애니메이션
1. 로딩 최적화
이미지 최적화
- 이미지 리사이징 (보통 cdn 사용) cf. next Image도 결국 내부 서버를 사용하는 것
ㄴ 이미지를 canvas에 넣어 자르기 => blob으로 바꾼 후 URL로 변환 (=> 브라우저 딴에서 최적화 가능)
ㄴ 가장 효과가 좋다 - 이미지 포맷 최적화
jpg, png, webp, svg, gif, avif
svg는 아이콘에서 많이 씀(벡터) - 최적화를 잘 하지 않음. - 본래 사이즈가 작고, 변환 시에 깨져보일 수 있기 때문에
손실압축 vs 비손실 압축
jpg : 손실 압축
png: 비손실 압축
jpg, png <-> webp, avif (브라우저 호환이 안되는 경우가 있다)
요청 헤더에 accept를 보면 어떤 걸 지원하는지 볼 수 있다.
cdn 사용시 f_auto를 넣어주면 최고로 효율 좋은걸로 바뀜. (보통 avif. 이미지 용량이 더 작은 경우가 많음)
cdn 사용시 브라우저 호환성은 크게 고려하지 않아도 가능하다. - 이미지 퀄리티 최적화
- 리소스 크기를 줄이는 것이 퀄리티 최적화
- 이미지를 75%에 가깝게 줄이는 것은 잘 티가 안 난다. ( hero image는 티가 조금 날 수 있으니 주의하장)
- 사진 안에 글자가 있는 경우엔 좀 더 깨져보일 수 있어 90%정도? - response image
- 모바일, pc일 ㄹ때 다른 이미지 사이즈 or 포맷 적용 가능
- picture 태그 사용 (max width 설정) - LQIP (low quality image placeholder)
- fcp를 비약적으로 올리는 방법
- 굉장히 낮은 퀄리티 (1%)를 부르고 - 백그라운드에서 본래 이미지 로드 - 로드 끝나면 원본으로 대체
- 단점: network 페이로드 증가
- gatsby 에서 이걸 지원함. - Sprite image
- 하나의 이미지에 다양한 것을 넣어두고 위치를 조정하는 방식
- 네트워크 payload를 줄임
- 한계: 하나의 이미지만 독립적인 제어 불가능
- http2의 등장으로 잘 안 쓰게 되었다. (50개를 병렬적으로 요청할 수 있어서 굳이 네트워크 payload를 줄일 필요가 없어짐) - lazy loading
- 현재 내가 화면에 보이지 않는 이미지들은 안 부르고 있다가 스크롤 내릴 때 부름
- img 태그의 loading="lazy" 속성 넣어주기
(cf. 윈도윙과 개념차이 비교하기)
next image를 왜 쓰면 안될까?
클라이언트 서버를 이용하기에 ec2가 터지거나 매우 느려질 수 있다. 이미지 최적화하느라고 서버사이드 렌더링이 느려질 수 있다.
그래서 사용자가 많은 서비스에선 잘 이용하지 않는다.
웹폰트 최적화
- FOUT(flash of unstyled text), FOIT(flash of invisible text)
: 브라우저에서 기본적으로 뭐를 지원해주는지가 다름. IE는 FOUT가 기본, 나머지는 FOIT가 기본
: font-display 속성을 활용하자 (swap) - 포맷 최적화
: woff2 가 젤 좋다. - font subset
: 잘 안쓰는 글자 제거
dynamic font subset : 쓰이는 것들만. font-face의 unicode 속성 사용
번들 최적화 (script 최적화)
- webpack을 통한 minify, uglify
: next에선 자동으로 해줌. webpack을 쓴다면 거기서 관련 설정 해줘야 함. - code splitting
: 사용하는 곳에서만 사용되는 script를 부르는 것
: next에선 자동으로 해줌. webpack을 쓴다면 거기서 관련 설정 해줘야 함. - 경량화된 패키지 사용
: ex) moment.js -> dayjs , react -> preact (react는 자체는 작지만 dom이 매우 크당)
: 라이브러리 도입 전에 한번 검토해보는게 좋다. => https://bundlephobia.com/
Bundlephobia | Size of npm dependencies
Bundlephobia helps you find the performance impact of npm packages. Find the size of any javascript package and its effect on your frontend bundle.
bundlephobia.com
- 트리 세이킹
: tree shaking은 cjf에서 안되고 esm에서 된다. (차이점 : 컴파일 시점 vs 런타임 시점)
ejs는 ~~시점에 ~~하기 때문에 실제로 쓰고있는지 아닌지를 알 수 있음.
ex) lodash (cjf으로 제공하고 있는 라이브러리) vs lodash-es (esm으로 제공하고 있는 라이브러리)
debounce 함수만 불러오기 위해선 lodash-es를 써야함. 전자는 다 불러옴. - http 압축
: gzip, brolti
: nginx에서 gzip으로 서빙을 하고 압축해제를 한다
: brolti보다 gzip이 성능이 더 좋다.
gzip vs brolti 알아보기
렌더링 최적화
memo
React.memo, useMemo, useCallback
cf. 왜 리액트는 상태가 같던 다르던 무조건 리렌더링하는걸까?
부모 업데이트 -> 자식 업데이트 되는 구조 때문에!
리액트 훅은 함수이기 때문에 부모 컴포넌트가 호출되면 자식의 함수들도 호출되는 것.
- memo를 쓰려면 props가 예전 상태와 같아야 한다. (다르면 무조건 다시 불러옴)
- 그렇다면 props가 다를때 최적화는 어떻게 할 수 있을까?
-> windowing!
windowing
정말 많은 리스트를 불러올 때 유용하다
보통 라이브러리 많이 사용함. ex. react-window, react-vertualize...
windowing vs 무한스크롤?
무한스크롤은 네트워크를 최적화하는 방식 렌더링을 최적화 하지 않는다. dom에 다 남아있다.
windowing은 내가 보고있는 부분만 렌더링할 수 있도록 한다.
windowing과 무한스크롤을 같이 쓰면 좋은건가?
하드웨어 가속
- will-change
: 변할 것임을 미리 알려준다.
원리는?? - translate3d(), scale3d()
: dom은 대부분 cpu를 이용해 렌더링하는데, translate3d() & scale3d()는 gpu를 사용한다. (z축에 0을 입력하면 translate와 동일)
reflow / repaint
- width, height, left, top, right, bottom, ... : reflow를 일으킴 (reflow 일으키는 건 외우자)
https://boxfoxs.tistory.com/409
그래서 transform을 써야한다!! (no layout, no paint) 어떻게? - No Layout / No Paint
: tranform, opacity, cursor, orpahns, perspective
ex) display > visibility > opacity 순서로 부하를 일으킨다.
- opacity 0.99
: opacity는 no layout, no paint이기 때문에 이를 적극 활용하기 위한 특이한 기법
워커에서 받고 그걸 독립적으로 그리기?
* 캐시 *
캐시 저장의 주체가 각각 다르다
web cache (중요!)
저장주체 : web(브라우저)
헤더에 있다! Cache-Control ( 정적 파일을 캐시해준다) cf. max-age는 유효기간. 보통 1년이 최대
- css, js, image : max-age를 최대로 잡는다. (이래도 되는 이유는?! = hash값으로 저장되기에 이름이 항상 유니크하다. 이름 기준으로 캐시를 잡기에 이름이 바뀌면 캐시 다 소용없다. 빌드할 때마다 청크들의 이름이 바뀌기 때문에 1년으로 해놔도 다 사라짐.)
- html : max-age 0 or no-cache 로 잡는다. = 캐시를 안한다는 뜻 아님! = 검증을 한다. (html은 이름이 고정되어있어서 무조건 캐시되면 안된다)
Cache-Control이 없는 경우
- purist cache = 캐시를 설정하지 않았을 때 브라우저에서 임의로 설정하는 캐시 = 5분 = 5분 지나면 검증하고 캐시
왜 어떤건 cache control이 있고 어떤건 업음?
이름이 안바뀌는 애들은 cache를 적용하지 않는다. (Cache-Control: no-cache)
max-age 끝나면 실제로 바뀌었는지 아닌지를 검증을 한다. (Etag, Last-Modified 값을 기준으로 저장을 해뒀다가 이게 바뀌면 캐시가 바뀌었음을 인지) 바뀌었으면 다시 요청해서 다시 다운로드. 안 바뀌었으면 예전꺼씀.
max-age 동안에는 검증 조차도 안하고 무조건 캐시한다. (바뀌었어도 캐시되어있다)
요청 시에 if-Modified-Since와 if-None-Match라는 이름으로 값을 넘김 - 이게 Etag와 Last-Modified가 됨!
검증된 캐시에 대한 응답 = 304 not Modified
검증안하고 무조건 캐시 = 200
no-cache vs no-store
- no-cache === max-age =0 (캐시는 함)
- no-store : 절대 캐시하지 않겠다 (얘가 짱짱)
must-revalidate -> max-age = 0
pragma -> http1.1, 2 이전 버전에서는 pragma로 캐시를 했어야 합니다 *****(가산점 대답)
(pragma에 대해 알아보자..!)
stale-while-revalidate
: swr, React query도 이 전략을 쓰고 있다! (리액트 쿼리의 캐시 전략)
: max-age보다 커야함. 그래야 의미가 있다. 작으면 무시됨
: max-age=1, stale-while-revalidate=59 -> 1초까지는 무조건 캐시, 1~60초까지는 일단 예전 컨텐츠 보여주는데, 그 때 백그라운드에서 검증 후 새로운 컨텐츠 받아놓음. -> 그 다음 요청 때 검증된 컨텐츠 뱉음 -> 60초 뒤부터는 컨텐츠를 그 때 뱉음
: 예전께 보이다가 새로운 걸로 샤샥 바뀌는게 이 전략 때문.
: 이걸 안 쓰면 요청 했을 때 검증부터 시작
s-maxage, private, public
: cdn과 관련된 캐시옵션
: s-maxage = cdn에서 캐시하겠다
: private = cdn에 캐시를 만들지 않겠다.
: public = 디폴트 옵션. cdn에 캐시만들어도 상관없다
cache busting 알아보기
https://jongminlee0.github.io/2021/01/23/cachebusting/
캐시 속성 다 말해볼 수 있어야함..ㅋㅋㅋㅋ
api cache
저장주체: 서버
Redis 등을 이용함.
api 내부에서 db를 더 조회하지 않도록 캐시하는 것.
서버 쪽에서의 cache
react-query cache
저장된 주체는 메모리 (즉, 코드에 저장)
js 객체로 저장하고 있다. (queryClient안에 key를 기반으로 저장)
0으로 하면 새로고침할때까지만 저장
cache에 no-store 주는게
ssr vs ssg
요청할 때를 생각
ssr은 새로고침할 때마다 서버에서 계속 컨텐츠 다시 만들어서 뱉어줌 = 동적인 동작이 됨.
ssg은 빌드할 때 만들고 그 결과물을 보여주는 것
isr은 갱신 시간을 정해줄 수 있다. 완전히 정적도 아니고 부하가 너무 심하게 걸리지도 않음. (stale while revalidate 방식을 씀)