Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 우테캠 회고록
- react customHook 예시
- 프로그래머스 K_Digital Training 프론트엔드
- 투포인터알고리즘 js
- Vue3
- TypeScript 문법 소개
- react 프로젝트 리팩토링
- 모던 자바스크립트 Deep Dive TIL
- 인프런 자바스크립트 알고리즘 문제풀이
- 머쓱이
- 모던 자바스크립트 Deep Dive
- 리팩토링 회고
- useEffect return
- K_Digital Training
- 프로그래머스 데브코스
- 모던 javascript Deep Dive
- 모던 자바스크립트 딥다이브
- KDT 프로그래머스 데브코스 프론트엔드
- KDT 프로그래머스
- 백준 js
- 백준 node.js
- 프로그래머스 K_Digital Training
- useRef 지역 변수
- 프로그래머스 데브코스 프론트엔드 TIL
- Vue3 Router
- Frontend Roadmap
- frontend roadmap study
- 개발자 특강
- 모던 자바스크립트 TIL
- 프로그래머스 데브코스 프론트엔드
Archives
- Today
- Total
프론트엔드 개발자의 기록 공간
[프론트엔드 성능 최적화 가이드] 정리 본문
내 마음대로 정리하는 "프론트엔드 성능 최적화 가이드"
프론트엔드를 개발하다보면 어떤 부분에서 최적화를 할 수 있을까?에 대한 고민을 많이 하게 됩니다.
이론적으로 캐싱을 활용하거나, 로딩 성능과 렌더링 성능을 하면 된다고 생각을 하지만,
실무에서 어떻게 확인하고 적용할 수 있을까? 이론적으로 부족한 부분은 무엇일까에 대한 고민으로 해당 책을 구매하여 읽게 되었습니다.
해당 책을 읽고 제가 몰랐던 정보나, 유용하다고 생각하는 핵심 부분만 마음대로 정리해서 기록해 보았습니다.
비교적 짧게 정리해서 축약된 부분이 많으니 필요시 해당 키워드로 검색해서 따로 찾아보면 좋을 것 같습니다.
소개
- 웹 성능을 결정하는 요소는 크게 로딩 성능과 렌더링 성능으로 나눌 수 있다.
- 로딩 성능은 서버에 있는 웹 페이지와 웹 페이지에 필요한 기타 리소스를 다운로드할 때의 성능
- 웹페이지에서 사용되는 이미지, css, 파일의 크기가 너무 크면 다운로드하는데 시간이 오래 걸림
- 로딩 성능을 개선하는 가장 좋은 방법은 다운로드해야하는 리소스 수를 줄이거나 크기를 줄이는 방법과 코드를 분할하여 다운로드를 하거나 우선순위를 통해 중요한 리소스부터 다운로드하는 식으로 해결할 수 있다.
- 렌더링 성능은 다운로드한 리소스를 가지고 화면을 그릴 때의 성능
- 렌더링 성능에 크게 영향을 주는 것은 자바스크립트 코드이다.
- 코드를 얼마나 효율적으로 작성했느냐에 따라 화면이 그려지는 속도와 인터랙션의 자연스러운 정도가 달라진다.
- 렌더링 성능을 개선하는 방법은 다양하지만, 브라우저의 동작 원리나 프레임워크의 라이프사이클 등 웹 개발 기본 지식을 이해를 통해 개선할 수 있다.
1장 블로그 서비스
최적화 기법
- 이미지 사이즈 최적화
- 이미지 CDN
- 적절한 이미지 사이즈로 최적화
- 코드 분할 & 지연로딩 (lazy로딩)
- 번들 파일 분리
- 페이지/컴포넌트 지연로딩을 위해 lazy 로딩, suspense컴포넌트로 감싸기
- 텍스트 압축 (js 파일 코드등을 압축 및 난독화)
- 병목 코드 최적화
- 불필요한 반복문 등의 코드 최적화
Lighthouse
- First Contentful Paint(FCP) : 페이지가 로드될 때 브라우저가 DOM 콘텐츠의 첫 번째 부분을 렌더링하는데 걸리는 시간에 관한 지표 (페이지에 진입하여 첫 콘텐츠가 뜰때까지의 시간)
- Largest Contentful Pating(LCP) : 페이지가 로드될 떄 가장 큰 이미지나 파일 요소가 렌더링되기까지 걸리는 시간
- Time to Interactive(TTI) : 사용자가 페이지와 상호 작용이 가능한 시점까지 걸리는 시간(버튼 클릭 등)
- Cumulative Layout Shift(CLS) : 페이지 로드 과정에서 발생하는 예기치 못한 레이아웃 이동을 측정한 지표(이미지 요소가 늦게 렌더링되면서 기존 UI 요소의 위치나 크기가 순간적으로 변화는 현상)
2장 올림픽 통계 서비스 최적화
브라우저 렌더링 과정
- DOM+CSSOM > 렌더 트리 > 레이아웃 (요소의 위치나 크기 계산) > 페인트 > 컴포지트 (레이어 합성 단계)
- 리플로우, 리페인트
- 리페인트시에는 레이아웃 단계를 건너뜀
- 리플로우와 리페인트를 피하는 방법 ⇒ transform, opacity 속성을 이용하면 해당 요소를 별도의 레이어로 분리하고 작업을 GPU에 위임하여 처리함으로써 레이아웃 단계와 페인트 간계를 건너뛸 수 있다. 이를 하드웨어 가속 이라고 함
- 리플로우와 리페인트를 일으키는 width, height, color 등의 속성이 아닌 transfrom, opacity 속성을 이용하는 것이 GPU에 의해 처리되어 레이아웃 단계와 페인트 단계없이 스타일을 변경할 수 있기 때문에 애니메이션 성능이 더 좋다
컴포넌트 지연로딩
- Suspense 컴포넌트와 lazy함수를 불러와서 사용
- 페이지 로드시 당장 필요없는 컴포넌트 코드가 번들에 포함되지 않아, 로드할 파일의 크기가 작아지고 초기 로딩 속도나 자바스크립트의 실행 타이밍이 빨라져서 화면이 더 빨리 표시된다는 장점
- 하지만, 해당 컴포넌트 사용시 네트워크를 통해 해당 코드를 새로 로드해야 하며 로드가 완료되어야만 해당 컴포넌트를 사용할 수 있기 때문에 한계가 있다. 즉 해당 컴포넌트를 불러오기까지 약간의 지연이 발생한다는 단점이 존재
- 사전 로딩을 통해 해결 (모듈을 미리 로드하는 기법)
- 하지만 어느시점에 모듈을 미리 로드해 둘지 애매함. (예를들어 클릭시 모달이 열리고 해당 컴포넌트 내에서 모듈이 필요해서 모달이 열리기전 모듈을 미리 로드해둔다했을때, 모달 클릭을 안하면 미리 로드하는 의미가 없기때문에)
- 이때 고려할 수 있는 타이밍이 두 가지 존재
- 사용자가 버튼 위로 마우스를 올렸을 때 클릭을 할 가능성이 있다는 것을 염두하고 모듈 로드
- mouse hover와 같은 자바스크립트 이벤트를 통해 import 함수 실행
- 최초에 페이지가 로드되고 모든 컴포넌트의 마운트가 끝난 시점에 로드
- useEffect에서 모듈 로드
useEffect(() => { const component = import('./component/ImageModal') }, [])
- 사용자가 버튼 위로 마우스를 올렸을 때 클릭을 할 가능성이 있다는 것을 염두하고 모듈 로드
이미지 사전 로딩
- 이미지는 이미지가 화면에 그려지는 시점, 즉 html 또는 css에서 이미지를 사용하는 시점에 로드
- src가 할당되는 순간 이미지 로드 (data-src로 임의로 제공후 useEffect안에서 src 리소스 제공)
- 자바스크립트로 이미지를 직접 로드하는 방법 존재 ⇒ 자바스크립트 Image 객체 사용
// 모달 컴포넌트와 이미지를 미리 로드
useEffect(() => {
const component = import('./component/ImageModal')
const img = new Image()
img.src = 'https://~~'
}, [])
3장 홈페이지 최적화
이미지 지연 로딩
- 가장 처음으로 사용자에게 보이는 콘텐츠인데 나중에 로드되면, 사용자가 첫 회면에서 오래 머물게 되므로 사용자 경험에 좋지 않다.
- 이를 해결하기 위해 이미지 지연 로딩 기법이 존재 ⇒ 뷰포트에 이미지가 표시될 때 이미지 리소를 로드하는 방법
- 스크롤 이벤트를 통해 스크롤 위치가 이미지 위치까지 도달했을 때 이미지 로드 ⇒ 스크롤 이벤트가 많이 발생 시키므로 브라우저의 메인 스레드에 무리가 간다. throttle 방식으로 개선할 수 있지만, 좋지 않은 방법
- Web API Intersection Observer 이용 ⇒ 특정 요소를 관찰(observe)하다가 스크롤 시, 해당 요소가 화면에 들어왔는지 아닌지 알려준다.
Intersection Observer 상세 보기
1. Intersection Observer의 주요 구성 요소
- root: 교차 여부를 판단할 기준 요소입니다. null이면 브라우저의 뷰포트를 기준으로 사용합니다.
- rootMargin: root의 경계를 조정할 수 있는 속성입니다. (CSS margin과 유사하게 작동)
- threshold: 관찰 대상이 root와 교차하는 비율(0~1 사이)을 지정합니다.
- callback: 조건이 충족되었을 때 실행되는 함수입니다.
2. Intersection Observer 동작 원리
Intersection Observer의 내부 동작은 다음과 같습니다:
(1) 브라우저의 Layout 단계와 함께 동작
- 브라우저는 화면을 렌더링할 때 Layout(요소의 위치 계산), Paint, Composite 등의 단계를 거칩니다.
- Intersection Observer는 브라우저의 Layout 단계에서 관찰 대상의 위치와 root의 위치를 계산합니다.
(2) 관찰 대상과 root의 교차 여부 확인
- 브라우저는 관찰 대상(target)과 root의 Bounding Box(경계 박스)를 계산합니다.
- Bounding Box 계산: 각 요소의 getBoundingClientRect() 값을 통해 현재 위치를 계산합니다.
- 그다음, 두 박스의 교차 영역을 구합니다. 이 교차 영역이 threshold 조건을 만족하는지 판단합니다.
(3) Intersection Ratio 계산
- Intersection Observer는 교차 영역의 비율을 계산합니다.
- 예시: 교차 영역의 넓이 ÷ 타겟 요소의 전체 넓이 = Intersection Ratio
- 이 비율이 threshold 조건을 만족하면 callback 함수가 실행됩니다.
(4) 조건 충족 시 callback 실행
- 관찰 대상이 root와 교차하는 상태(즉, threshold 조건)를 충족하면 Intersection Observer는 callback 함수를 비동기적으로 호출합니다.
- 교차 상태의 변경 여부를 IntersectionObserverEntry 객체를 통해 제공합니다.
3. Intersection Observer가 성능에 유리한 이유
- 비동기적 처리: Intersection Observer는 메인 스레드를 차단하지 않고 별도의 계산을 처리합니다.
- 레이아웃 재계산 최소화: 스크롤 이벤트를 지속해서 감지하는 대신 브라우저가 최적화된 시점에 관찰 영역을 업데이트합니다.
- 하드웨어 가속 활용: 브라우저는 Intersection Observer의 내부 계산을 GPU 레벨에서 최적화할 수 있습니다.
4. 간단한 동작 흐름
- Intersection Observer가 생성될 때 root, rootMargin, threshold가 설정됩니다.
- 관찰할 대상(target) 요소를 Observer에 등록합니다.
- 브라우저가 Layout 단계에서 교차 여부를 계산하고 threshold 조건을 확인합니다.
- 조건을 만족하면 callback 함수가 실행됩니다.
이미지 사이즈 최적화
- 이미지 사이즈가 크면 다운로드에 많은 시간이 걸린다. 이미지를 사용하는 크기에 맞는 사이즈를 다운받아서 최적화를 할 수 있다.
- 비트맵 이미지 포맷중 대표적인 포맷은 PNG, JPG(JPEG), WebP
- PNG : 무손실 압축 방식으로 원본을 훼손 없이 압축
- JPG(JPEG) : 압축 과정에서 정보 손실 발생. 하지만 그만큼 이미지를 더 작은 사이즈로 줄일 수 있다. 고화질이 필요한게 아니라면 일반적으로 사용
- WebP : 무손실 압축과 손실 압축을 모두 제공하는 최신 이미지 포맷. PNG, JPG에 비해 대단히 효율적으로 이미지 압축 가능. WebP 공식문서에 따르면 PNG 대비 26%, JPG 대비 25~34% 더 나은 효율이 있다고 함. 하지만 최신 이미지 파일 포맷이기 때문에 지원하지 않는 브라우저가 있기 때문에 지원율을 잘 확인해서 사용해야한다.
- 브라우저 호환성 문제가 존재. WebP로만 이미지 렌더링할 경우 특정 브라우저에서는 제대로 렌더링되지 않을 수 있다. 이런 문제를 해결하려면 단순 img태그만 이미지를 렌더링하면 안되며, picture 태그를 사용해야한다.
// webp가 우선적으로 렌더링. 브라우저가 지원하지 않을 경우 jpg 이미지 렌더링 <picture> <source srcset="some-image.webp" type="image/webp" /> <img src="other-image.jpg" /> </picture>
- <picture> 태그는 <img> 요소의 다중 이미지 리소스(multiple image resources)를 위한 컨테이너를 정의할 때 사용합니다.
폰트 최적화
- 커스텀 폰트가 다운로드 되기전 기본폰트가 보여지다가 커스텀 폰트가 다운로드되어 폰트가 바뀌면서 깜박이는 모습을 개선하기 위해 폰트 최적화가 필요하다.
- 폰트의 변화로 발생하는 현상을 FOUT, FOIT라 한다.
- 폰트를 최적화하는 방법은 크게 두 가지가 있다. 폰트 적용 시점을 제어하는 방법, 폰트 사이즈를 줄이는 방법
- CSS의 font-display 속성을 이용하면 폰트가 적용되는 시점 제어 가능
- 폰트 포맷별 파일크기 EOT > TTF/OTF > WOFF > WOFF2
- WOFF(Web Open Font Format) 웹을 위한 폰트 확장자 포맷 이용. 이 포맷은 TTF 폰트를 압축하여 웹에서 더욱 빠르게 로드하도록 만듦.
- 브라우저 호환성 문제가 존재. @font-face 우선순위 적용을 통해 호환이 안되는 포맷의 경우 대체 포맷의 폰트로 지정
- @font-face { font-family: 폰트명 src : url (경로.woff2) format('woff2'), url (~) format(경로.ttf) format('truetype') }
캐시 최적화
- 메모리 캐시 : 메모리에 저장하는 방식(RAM),
- 디스크 캐시 : 파일 형태로 디스크에 저장하는 방식
- http 응답헤더에 Cache-Control 헤더의 유무에 따라 브라우저 캐시가 적용됨. 이는 서버에서 설정되며 옵션에 따라 해당 리소스를 어람나 캐시할지 판단
- Cache-Control
- 리소스의 응답 헤더에 설정되는 헤더. 브라우저는 서버에서 이 헤더를 통해 캐시를 어떻게, 얼마나 적용해야하는지 판단
- no-cache : 캐시 사용하기 전 서버에 검사 후 사용 (max-age: 0과 동일)
- no-store: 캐시 사용안함
- max-age: 캐시의 유효 시간
캐시 적용
캐시 유효 시간이 만료되면 브라우저는 기존에 캐시된 리소스를 그대로 사용할지, 새로 요청해야하는지 서버에 확인합니다. 이때 변경되지 않았다면 서버에서는 304 상태 코드를 응답으로 보냅니다.
- 캐시된 리소스와 서버의 최신 리소스가 같은지 어떻게 알까요? ⇒ 서버에서는 응답 헤더에 있는 Etag값과 최신 리소스의 Etag 값을 비교하여 판단합니다. 같을 경우 304 응답 코드를, 다를 경우 새로운 Etag값과 최신 리소스를 함께 브라우저로 반환합니다.
적절한 캐시 유효 시간
- 일반적으로 HTML 파일에는 no-cache 설정을 적용합니다. HTML이 캐시되면 캐시된 HTML에서 이전 버전의 자바스크립트나 CSS를 로드하게 되므로 캐시 시간 동안 최신 버전의 웹 서비스를 제공하지 못합니다.
불필요한 CSS 제거
- Lighthouse Opportunities 섹션의 “Reduce unused CSS” 항목을 보면 사용하지 않는 CSS를 제거하면 얼마나 줄일 수 있는지 용량을 알려줌
- 개발자 도구에서 Coverage 패널에서는 자바스크립트 및 CSS 리소스에서 실제로 실행한느 코드가 얼마나 되는지 비율을 알려줍니다. 따라서 불필요한 코드가 얼마나 있는지 확인 할 수 있습니다.
PurgeCSS
- 사용하지 않는 CSS 코드 제거를 위한 툴 중 하나로 파일에 들어있는 모든 키워드를 추출하여 해당 키워드를 이름으로 갖는 CSS 클래스만 남기고 나머지 클래스는 지우는 형식으로 최적화합니다.
4장 이미지 갤러리 최적화
레이아웃 이동 피하기 CLS(Cumulative Layout Shift)
- 화면상의 요소 변화로 레이아웃이 갑자기 밀리는 현상을 말합니다. 예를들어 이미지가 늦게 로드될 때 해당영역에 있던 UI 요소들을 밀어내면서 이미지가 화면에 그려지는 현상입니다.
- Lighthouse에서 레이아웃 이동이 얼마나 발생하는지를 나타내는 지표로 CLS 성능 지표 점수가 있습니다.
- CLS는 0부터 1의 값을 가지며, 레이아웃 이동이 발생하지 않은 경우 0, 발생할수록 높은 점수를 가집니다. 권장하는 점수로는 0.1 이하입니다.
- Performance 탭의 Experience 섹션을 보면 Layout Shift가 표시되고 해당영역에 커서를 올려놓으면 레이아웃 이동을 유발한 요소를 표시해줍니다.
레이아웃 이동의 원인
- 사이즈가 미리 정의되지 않은 이미지 요소
- 사이즈가 미리 정의되지 않은 광고 요소
- 동적으로 삽입된 콘텐츠
- 웹 폰트(FOIT, FOUT)
레이아웃 이동 해결
- 요소의 사이즈를 지정. 이미지 사이즈를 고정으로 할 수 없는 경우는 이미지의 너비, 높이 비율로 공간을 지정하면 됩니다.
- 이미지 크기를 비율로 설정하는 방법은 padding으로 박스를 만든 뒤, 그 안에 이미지를 absolute로 띄우는 방식이 있습니다. (aspect-ratio css속성도 존재)
<div> <img src=".."/> </div>
div { position: relative; width: 160px; padding-top: 56.25% /* 16:9 비율 */ } img { postion: absolute; width: 100%; height: 100%; top: 0; left: 0; }
728x90
'개발지식' 카테고리의 다른 글
[FE_Roadmap] Web Components (2) | 2024.06.02 |
---|---|
requestAnimationFrame 사용법 (1) | 2024.05.26 |
[FE_Roadmap] writing css & testing your apps (0) | 2024.03.02 |
[FE_Roadmap] Pick a Framework (1) | 2024.02.18 |
[FE_Roadmap] Module Bundlers (0) | 2024.02.13 |
Comments