웹 성능
성능 측정을 해야하는 이유
성능 측정을 해야 하는 이유는 사용자 경험과 기업의 이익에 직접적인 영향을 미치기 때문이다.
아카마이의 이커머스 업계 성능 현황 보고서에 따르면,
지연 속도가 증가할수록 이탈률이 급격히 증가하고 구매 전환률이 감소하는 경향이 있다.
예를 들어, 2초의 지연이 발생할 경우 최소 62%의 이탈률 증가와 25%의 구매 전환률 감소가 나타난다.
실제로 핀터레스트는 성능 최적화를 통해 대기 시간을 40% 줄이고, SEO 트래픽을 15% 증가시키며, 가입 전환율을 15% 향상시켰고
이러한 사례들은 성능이 서비스 이용률과 기업의 이익에 미치는 영향을 잘 보여준다.
따라서 프론트엔드에서 성능 측정이 필요한 이유는
사용성을 개선해서 이익을 증대시키기 위함이라고 할 수 있다.
프론트엔드에서 측정해야 하는 성능은 무엇일까?
- 로딩 속도
- 페이지가 사용자에게 완전히 로드되는 시간으로
로딩 속도는 사용자가 웹사이트를 방문할 때 첫인상을 결정짓는 요소이다. - 로딩 속도가 느리면 사용자 이탈률이 높아지고,
SEO(검색 엔진 최적화)에도 부정적인 영향을 미친다.
- 페이지가 사용자에게 완전히 로드되는 시간으로
- 렌더링 시간
- 렌더링 시간은 사용자가 페이지를 볼 수 있게 되기까지의 시간으로
JavaScript 실행, CSS 적용, 이미지 로딩 등이 포함된다. - 렌더링 시간이 길면 사용자에게 콘텐츠가 늦게 표시되어 사용자 경험 저하될 수 있다.
- 렌더링 시간은 사용자가 페이지를 볼 수 있게 되기까지의 시간으로
- 메모리 누수
- 애플리케이션이 사용하지 않는 메모리를 계속 점유하는 현상으로
메모리 누수가 발생하면 성능 저하와 크래시를 유발할 수 있다.
- 애플리케이션이 사용하지 않는 메모리를 계속 점유하는 현상으로
- 응답 시간
- 응답 시간은 사용자와 서버 간의 상호작용에서 중요한 역할을 한다.
- CPU 사용량
- JavaScript 실행 등으로 인한 CPU 자원 사용량이 높으면
성능 저하와 배터리 소모를 초래할 수 있다.
- JavaScript 실행 등으로 인한 CPU 자원 사용량이 높으면
- 프레임 속도
- 애니메이션과 전환의 부드러움을 측정하는 FPS(Frames Per Second)로
사용자에게 적합한 프레임 속도는 60FPS로
이보다 낮은 프레임 속도는 사용자에게 버벅임을 느끼게 해서 불쾌감을 줄 수 있다.
- 애니메이션과 전환의 부드러움을 측정하는 FPS(Frames Per Second)로
웹 성능 지표
FCP (First contentful paint)
웹 페이지가 로드될 때 사용자가 첫 번째 콘텐츠를 시각적으로 인식할 수 있는 시점을 측정하는 성능 지표로
FCP는 페이지의 초기 로딩 성능을 평가하는 데 중요한 역할을 한다.
- 일단 페이지가 그냥 흰 화면이 아닌, 무슨 요소라도 보이기 시작한 시점이기 때문에
사용자 입장에서는 실질적으로 로딩의 시작을 인지하기 시작하는 시점이다. - iframe 내부의 콘텐츠가 로드되더라도,
FCP는 페이지의 기본 콘텐츠가 사용자에게 표시되는 시점만을 고려하기 때문에 iframe의 로딩 속도가 페이지의 FCP에 영향을 미치지 않는다. -
양호: 1초 이하 개선 필요: 1초 ~ 2.5초 나쁨: 2.5초 이상 - 측정값이 나쁨으로 나오는 원인
- 렌더링 지연 자원
- 외부 CSS 파일은 페이지가 로드될 때 브라우저가 해당 파일을 다운로드하고 파싱해야 하므로, 이 과정이 완료되기 전까지 페이지의 렌더링이 지연될 수 있다.
- 동기적인 외부 JavaScript 파일은 브라우저가 해당 파일을 다운로드하고 실행하기 전까지 페이지의 렌더링을 중단한다. 이로 인해 페이지의 콘텐츠가 늦게 표시될 수 있다.
- 이미지 로딩 지연: 페이지의 시각적 요소가 사용자에게 표시되는 데 영향을 미칠 수 있다.
- 큰 이미지 파일 크기
- 낮은 이미지 압축률
- WebP 미사용
- lazy loading 미적용
- 폰트 로딩 지연: 폰트 로딩 지연은 텍스트 콘텐츠 표시를 지연시키는데 영향을 미칠 수 있다.
- 과도한 DOM 크기: DOM 요소가 많을수록 브라우저가 이를 파싱하고 렌더링하는 데 더 많은 리소스가 필요하기 때문에, 이는 페이지의 렌더링 속도를 저하시킬 수 있다.
- 렌더링 지연 자원
- 최적화 방법
- CSS 최적화
- Critical CSS: 페이지 렌더링에 필요한 CSS만 인라인으로 포함하고 나머지는 비동기로 로드한다.
- CSS 파일 최소화: 불필요한 CSS를 제거하고 파일 크기를 줄여 로딩 속도 향상시킨다.
- JavaScript 비동기 로딩
- defer 및 async 속성 사용: 스크립트 태그에
defer
또는async
속성을 추가해서 렌더링 차단을 방지한다. - 필요한 스크립트만 로드:
<head>
섹션에 필수적인 스크립트만 포함해서 초기 페이지 로드에 필요한 스크립트만 포함하고 나머지는 지연 로드한다.
- defer 및 async 속성 사용: 스크립트 태그에
- 이미지 최적화
- 포맷 최적화: WebP와 같은 최신 이미지 포맷을 사용한다.
- 지연 로딩: 화면에 보이지 않는 이미지는 지연 로딩해서 초기 로딩 속도 개선한다.
- 폰트 로딩 최적화
- 웹 폰트가 로드될 때까지 텍스트를 시스템 기본 폰트로 즉시 표시하도록하는
font-display: optional
과font-display: swap
를 사용해서 텍스트가 즉시 표시되도록 한다.
- 웹 폰트가 로드될 때까지 텍스트를 시스템 기본 폰트로 즉시 표시하도록하는
- 과도한 DOM size 최소화
- CSS 최적화
LCP (Largest Contentful Paint)
LCP는 Core Web Vitals 중 하나로,
페이지의 메인 콘텐츠가 렌더링된 시점까지 소요된 시점을 측정하는 성능 지표로
가장 큰 컨텐츠를 기준으로 한다.
- 사용자가 감지하는 로드 속도의 지표로 중요한 역할로
즉 LCP가 빠르다면 사용자가 해당 페이지를 사용할 수 있다고 인지하는데 도움이 된다. -
양호: 2.5초 이하 개선 필요: 2.5초 ~ 4초 나쁨: 4초 이상 - 측정값이 나쁨으로 나오는 원인
- LCP 요소 로딩 지연
- LCP 요소가 이미지나 비디오인 경우,
미디어 파일이 로드되는 데 시간이 걸리면 LCP가 지연된다. - LCP 요소가 텍스트 블록인 경우,
텍스트가 렌더링되는 데 시간이 걸리면 LCP가 지연된다.
- LCP 요소가 이미지나 비디오인 경우,
- 서버 응답 시간 지연
- LCP 요소 데이터 전송 지연
- 서버 측 처리 시간 지연
- 렌더링 차단 요소
- LCP 요소 렌더링을 방해하는 요소 (외부 CSS 파일, 동기적인 외부 JavaScript 파일)
- LCP 요소 로딩 지연
LCP의 4가지 구성요소
모든 페이지의 LCP는 다음 4가지 하위 카테고리로 구성된다.
LCP 시간은 간격이나 중복 없이 누적이 되고
이 하위 카테고리를 모두 합하면 전체 LCP 시간이 된다.
LCP를 최적화할 때는 이러한 하위 부분을 개별적으로 최적화하는 것이 좋다.
하지만 모든 항목을 최적화해야 LCP를 개선할 수 있다.
한 부분에 적용된 최적화로 LCP가 개선되지 않고 절약된 시간이 다른 부분으로 이동하는 경우가 있기 때문이다.
예를 들어, 이미지 파일 크기를 줄이거나 최적화된 형식으로 전환하면
리소스 로드 시간은 줄어들 수 있지만 이미지가 화면에 표시되는 데 필요한 렌더링 시간이 길어질 수 있다.
즉, 절약된 로드 시간은 렌더링 지연으로 이동하게 되어 LCP 개선 효과가 상쇄될 수 있다.
따라서 모든 요소를 종합적으로 고려해서 최적화해야 LCP 개선 효과가 상쇄되는 것을 방지할 수 있다.
- 첫 바이트까지의 시간 (Time to first byte): 사용자가 페이지 로드를 시작한 시점부터 브라우저가 HTML 문서 응답의 첫 번째 바이트를 수신할 때까지의 시간이다.
- 리소스 로드 지연 (Resource load delay): TTFB와 브라우저가 LCP 리소스 로드를 시작하는 시점 사이의 시간이다.
- 리소스 로드 시간 (Resource load duration): LCP 리소스 자체를 로드하는 데 걸리는 시간이다.
- 요소 렌더링 지연 (Element render delay): LCP 리소스 로드가 완료된 시점과 LCP 요소가 완전히 렌더링된 시점 사이의 시간이다.
첫 바이트까지의 시간 (Time to first byte)
- 서버 성능 개선
- 서버 하드웨어 업그레이드: 더 빠른 CPU, RAM, SSD를 사용해서 서버 성능 향상시킨다.
- 서버 소프트웨어 최적화: 웹 서버 소프트웨어의 설정을 최적화해서 응답 속도 개선한다.
- 캐싱 활용
- 페이지 캐싱: 정적 페이지를 캐싱해서 서버가 매번 요청을 처리하지 않도록 한다.
- 오브젝트 캐싱: 데이터베이스 쿼리 결과를 캐싱해서 데이터베이스 부하 감소시킨다.
- CDN 사용
- 지리적 분산: CDN을 통해 사용자와 가까운 서버에서 콘텐츠를 제공해서 지연 시간을 감소시킨다.
- 정적 자원 캐싱: 이미지, CSS, JavaScript 파일을 CDN에 캐싱해서 빠른 로딩을한다.
- 데이터베이스 최적화
- 쿼리 최적화: 느린 쿼리를 분석하고 인덱스를 추가해서 성능을 향상시킨다.
- 데이터베이스 분산: 데이터베이스를 여러 개의 작은 데이터베이스로 나누어 관리해서 부하를 분산시킨다.
리소스 로드 지연 (Resource load delay)
loading="lazy"
속성을 삭제:loading="lazy"
속성은 이미지 로딩을 지연시키는 속성이다.
이를 삭제하면 이미지 로딩 지연을 방지하고 즉시 로딩을 시작할 수 있다.- 서버 응답 시간 단축: TTFB 최적화를 통해 서버 응답 시간을 줄이면 리소스 로딩 시작 시간을 단축해서 리소스 로드 지연을 줄일 수 있다.
- LCP 이미지 사전 로드: LCP 이미지가 외부 CSS 또는 JS 파일에서 참조될 경우
<link rel="preload">
태그를 사용해서 해당 리소스를 사전 로드하면 LCP 이미지 로딩 지연을 방지할 수 있다. - 미리 연결 Preconnect:
rel="preconnect"
는 현재 페이지에서 외부 도메인 리소스를 참고하는 것을 브라우저에게 알려 DNS, TCP, TLS 왕복에 필요한 시간을 단축한다.
리소스 로드 시간 (Resource load duration)
- 이미지 및 비디오 최적화
- 크기 조정: LCP 요소로 사용되는 이미지의 크기를 적절히 조정해서 불필요한 데이터 전송량을 줄여 로딩 시간을 단축시킬 수 있다.
- 스프라이트 이미지 사용: 아이콘 등의 이미지를 하나의 이미지로 묶어서 처리하면 이미지 파일 개수 자체를 줄이므로 리소스 요청 개수를 줄일 수 있다.
<picture>
태그 사용: picture 태그의 type 속성을 통해 사용자 환경에 맞는 타입의 이미지를 제공할 수 있다. 또한 media 속성을 사용해서 브라우저 사이즈에 맞는 이미지를 제공할 수 있다.- 비디오 최적화: 비디오 해상도 및 포맷을 최적화해서 비디오 파일 크기를 줄여 로딩 시간을 단축시킬 수 있다.
- 폰트 로딩 최적화
- 특정 언어/기호 글리프 폰트 사용: 필요한 글리프만 포함된 폰트를 사용해서 전체 폰트 파일 크기를 줄여 로딩 시간을 단축시킬 수 있다.
- 서브셋 폰트를 사용: 필요한 글자만 포함된 서브셋 폰트를 사용하면 폰트 파일의 크기를 줄여 LCP를 개선로딩 시간을 단축시킬 수 있다.
- 캐싱 활용: 캐싱을 활용해서 이전에 방문한 페이지의 리소스를 재사용하면 리소스 로딩 시간을 단축시킬 수 있다..
fetchpriority="high"
속성 사용:fetchpriority="high"
속성을 사용해서 LCP 이미지와 같은 중요한 리소스의 로드 우선순위를 높여 로딩 시간을 단축시킬 수 있다.- 미리 가져오기 Prefetch: 브라우저는
rel="prefetch"
가 적용된 리소스들을 가져와 캐시에 저장할 수 있다.
요소 렌더링 지연 (Element render delay)
- 서버 측 렌더링 (SSR): SSR을 통해 HTML 소스에 이미지를 포함시키면 브라우저가 이미지를 더 빠르게 렌더링할 수 있다.
<img>
요소 사용:<img>
요소를 사용해서 이미지를 로드하고,
data-src
와 같은 비표준 속성 사용을 피하면 브라우저가 이미지를 더 빠르게 렌더링할 수 있다.- 폰트 로딩 최적화
font-display: optional
또는font-display: swap
사용: 웹 폰트가 로드될 때까지 텍스트를 시스템 기본 폰트로 즉시 표시해서 텍스트 렌더링 지연을 방지한다.
FID (First Input Delay)
사용자가 링크를 클릭하거나, 버튼 클릭, JavaScript 기반 컨트롤을 사용하는 등
처음으로 상호 작용할 때부터 해당 상호작용에 대한 응답으로
브라우저가 실제로 인터렉티브한 동작을 시작하기 까지의 시간을 의미한다.
- **
지연 부분만 측정**해서 이벤트 처리 시간 자체나 UI 업데이트하는데 걸리는 시간은 측정하지 않는다. 입력 지연은 브라우저의 메인 스레드가 다른 작업을 하고 있어 사용자에게 응답할 수 없어 발생한다.실제 사용자가 필요하기 때문에 개발 환경에서 측정할 수는 없다.2024년 3월에 INP (Interaction to Next Paint)로 대체 보류 중인 지표이다.
💡 FID → INP 대체
FID가 INP로 대체된 이유는
사용자 경험을 더 정확하게 반영하기 위해서이다.
FID는 첫 번째 입력에 대한 지연만 측정하는 반면,
INP는 모든 입력 이벤트의 지연을 종합적으로 평가해서 페이지의 상호작용 성능을 더 잘 나타낸다.
INP (Interaction to Next Paint)
INP는 Core Web Vitals 중 하나로,
웹 페이지와 상호작용을 시도한 후 웹 페이지의 시각적 요소가 로드될 때 까지의 시간을 측정하는 성능 지표이다.
-
양호: 200ms 이하 개선 필요: 200ms ~ 500ms 나쁨: 500ms 이상 - 측정값이 나쁨으로 나오는 원인
- 긴 JavaScript 작업
- 메인 스레드 점유 시간 증가
- 사용자 입력에 대한 응답 지연
- 이벤트 핸들러 비효율
- 이벤트 핸들러 코드 실행 시간 증가
- 비효율적인 이벤트 처리 로직
- 과도한 계산 작업
- 복잡한 계산 또는 애니메이션 처리
- 메인 스레드 부하 증가
- 긴 JavaScript 작업
- 최적화 방법
- 긴 작업 분할
- 태스크는 브라우저에서 실행되는 모든 개별 작업을 의미하며, 50밀리초를 초과하는 태스크는 긴 작업으로 간주된다.
- 긴 작업은 기본 스레드가 사용자 상호작용에 빠르게 응답하지 못하도록 차단할 수 있다.
- JavaScript에서 최대한 적은 작업을 실행하고, 긴 작업을 분할해서 본 스레드가 다른 작업을 처리할 수 있도록해서 렌더링 업데이트와 사용자 상호작용을 더 빠르게 처리할 수 있다.
- Scheduler API를 사용해서 작업의 우선순위를 조정하고,
scheduler.yield()
를 통해 긴 작업을 분할하면서도 상호작용을 처리할 수 있다.
- 불필요한 JavaScript 제거
- 코드 중복을 줄이고 브라우저에서 제공하는 API를 활용한다.
- Chrome DevTools의 범위 도구를 사용해서 사용되지 않는 코드를 찾아 제거한다.
- 코드 스플리팅: Webpack과 같은 도구를 사용해서 필요한 코드만 로드한다.
- 대규모 렌더링 업데이트 방지
- JavaScript 실행 외에도 렌더링은 웹사이트의 반응성에 큰 영향을 미칩니다. 대규모 렌더링 업데이트는 사용자 상호작용에 느린 반응을 초래할 수 있습니다.
- CSS 컨테이너를 사용해서 자식 요소의 스타일을 격리하면,
부모 요소의 변경이 자식 요소에 영향을 미치지 않으므로 불필요한 렌더링을 줄일 수 있다. - DOM 조작 최소화: DOM을 자주 변경하지 않도록 해서 Reflow 및 Repaint를 줄인다.
- CSS 애니메이션 사용: GPU 가속을 활용한 CSS 애니메이션 사용한다.
- 긴 작업 분할
CLS (Cumulative Layout Shift)
CLS는 Core Web Vitals 중 하나로,
CLS는 페이지의 전체 라이프사이클 동안 발생하는 모든 예기치 않은 레이아웃 이동에 대해 가장 큰 레이아웃 이동에 대한 점수를 측정하는 성능 지표이다.
- 영향 받은 비율과 이동 거리 비율을 통해 계산된다.
-
양호: 0.1 이하 개선 필요: 0.1 ~ 0.25 나쁨: 0.25 이상 - 측정값이 나쁨으로 나오는 원인
- 이미지 및 비디오 크기 미지정
- 콘텐츠 로드 후 레이아웃 변경
- 광고 삽입
- 광고 로드 후 레이아웃 변경
- 폰트 로딩
- 폰트 로드 중 레이아웃 변경 (FOUT)
FOUT (Flash of Unstyled Text): 웹폰트가 로드될때까지 시스템의 기본 폰트를 보여주고 이후 reflow 해서 글꼴을 대체하는 방식
FOIT (Flash of Invisible Text): 웹폰트가 로드될 때까지 텍스트를 렌더링 하지 않다가 로드가 된 이후에 텍스트를 보여주는 방식
- 폰트 로드 중 레이아웃 변경 (FOUT)
- 동적 콘텐츠 삽입
- 콘텐츠 삽입으로 인한 레이아웃 변경
- 이미지 및 비디오 크기 미지정
- 최적화 방법
- 사전 정의된 크기
- 이미지 및 비디오 크기 설정:
width
와height
속성을 사용해서 레이아웃 이동을 방지한다. - 광고 및 동적 콘텐츠: 광고의 크기를 미리 정의해서 레이아웃 이동울 방지한다.
- aspect-ratio 속성: 이미지 및 비 이미지 요소에 가로세로 비율을 설정해서 레이아웃 이동을 줄일 수 있다.
min-height 설정: 빈 요소에 기본 높이 0px 대신 적절한 min-height를 설정해서 레이아웃 이동의 심각도를 줄일 수 있다.
- 이미지 및 비디오 크기 설정:
- 폰트 로딩 최적화
- 웹 폰트가 로드될 때까지 텍스트를 시스템 기본 폰트로 즉시 표시하도록하는
font-display: optional
과font-display: swap
를 사용해서 텍스트가 즉시 표시되도록 한다. - 폰트 로딩 API 사용: 폰트 로딩이 완료될 때까지 콘텐츠를 숨기고, 폰트가 적용된 후에 콘텐츠를 표시하면 레이아웃 이동을 방지할 수 있다.
- unicode-range 속성을 사용: 다국어 지원 시 필요한 폰트만 로드해서 불필요한 레이아웃 이동을 방지할 수 있다.
- 웹 폰트가 로드될 때까지 텍스트를 시스템 기본 폰트로 즉시 표시하도록하는
- 동적 콘텐츠 관리
- bfcache 활용: 브라우저의 bfcache (Back-Forward Cache)를 활용하면
이전에 방문한 페이지의 렌더링 상태를 유지해서 페이지 간 이동 시 CLS를 줄일 수 있다. - 고정된 위치 사용: 동적 콘텐츠의 위치를 고정해서 레이아웃 이동 방지
- 애니메이션 피하기: 레이아웃을 유도하는 CSS 속성을 애니메이션 처리하지 않고,
가능하면 transform 속성을 사용해서 애니메이션을 적용한다.
- bfcache 활용: 브라우저의 bfcache (Back-Forward Cache)를 활용하면
- 사전 정의된 크기
TTFB (Time to first byte)
TTFB는 브라우저가 페이지를 요청하고 첫번째 byte를 받는 사이의 시간을 측정하는 성능 지표로
TTFB는 주로 서버 성능과 직결된다.
- DNS 검색과 HTTPS로 연결되는 경우 TCP 핸드쉐이크, SSL 핸드쉐이크 연결을 구축하는 시간도 포함한다.
- TTFB는 핵심 웹 성능 지표가 아니다,
CSR은 빈 페이지를 받아 빠르지만 SSR은 이보다는 느리더라도 좋은 FCP, LCP 값을 가지기 때문이다. -
양호: 200ms 이하 개선 필요: 200ms ~ 500ms 나쁨: 500ms 이상 - 측정값이 나쁨으로 나오는 원인
- 느린 서버 응답
- 서버 하드웨어 성능 부족 (CPU, RAM, 디스크 I/O)
- 과도한 트래픽
- 데이터베이스 쿼리 비효율
- 서버 소프트웨어 설정 문제
- 서버 측 코드 비효율 (과도한 연산, 불필요한 로직)
- 네트워크 지연
- 서버와 사용자 간의 물리적 거리
- 네트워크 혼잡
- 불안정한 네트워크 연결
- 라우팅 문제
- CDN 미사용 또는 잘못된 설정
- CDN 미사용으로 인한 원격 서버와의 통신
- CDN 설정 오류 (캐싱 설정, 지역 설정 등)
- 캐싱 미흡:
- 서버 캐싱 미흡
- 브라우저 캐싱 미흡
- 압축 설정 오류
- 압축 미적용 또는 잘못된 압축 설정
- 압축 알고리즘 선택 오류 (Gzip, Brotli)
- HTTP/2 또는 HTTP/3 미사용
- HTTP/1.1 사용으로 인한 성능 제한 (헤더 압축, 다중 연결 등)
- 느린 서버 응답
- 최적화 방법
- 서버 성능 개선
- 서버 하드웨어 업그레이드: 더 빠른 CPU, RAM, SSD를 사용해서 서버 성능 향상시킨다.
- 서버 소프트웨어 최적화: 웹 서버 소프트웨어의 설정을 최적화해서 응답 속도 개선한다.
- 캐싱 활용
- 페이지 캐싱: 정적 페이지를 캐싱해서 서버가 매번 요청을 처리하지 않도록 한다.
- 오브젝트 캐싱: 데이터베이스 쿼리 결과를 캐싱해서 데이터베이스 부하 감소시킨다.
- CDN 사용
- 지리적 분산: CDN을 통해 사용자와 가까운 서버에서 콘텐츠를 제공해서 지연 시간을 감소시킨다.
- 정적 자원 캐싱: 이미지, CSS, JavaScript 파일을 CDN에 캐싱해서 빠른 로딩을한다.
- 데이터베이스 최적화
- 쿼리 최적화: 느린 쿼리를 분석하고 인덱스를 추가해서 성능을 향상시킨다.
- 데이터베이스 분산: 데이터베이스를 여러 개의 작은 데이터베이스로 나누어 관리해서 부하를 분산시킨다.
- 서버 성능 개선
TTI (Time to Interactive)
TTI는 페이지가 로드되기 시작한 시점부터 주요 하위 리소스가 로드되고
사용자 입력에 신속하고 안정적으로 응답할 수 있는 시점까지의 시간을 측정하는 성능 지표이다.
- FCP가 시작된 시점부터 사용자가 인터렉티브한 작업이 가능한 시점의 시간
- 좋은 사용자 경험을 위해 5초 미만이여야 한다.
- SSR과 같은 기술로 빠른 속도로 페이지가 인터렉티브 해보이게는 가능하지만 실제로는 인터렉티브 하지 않습니다. 메인 쓰레드가 차단되었거나 이러한 요소를 제어하는 JS 코드가 로드되지 않았기 때문입니다.
- FCP와 TTI 사이를 최소화하기 위해 최대한의 노력을 기울여야 한다.
-
양호: 5초 이하 개선 필요: 5초 ~ 7.3초 나쁨: 7.3초 이상 - 측정값이 나쁨으로 나오는 원인
- 렌더링 차단 요소
- CSS, JavaScript 파일
- 코드 분할 미흡
- 큰 JavaScript 파일 크기
- 파싱 및 실행 시간 증가
- 지연 로딩 미흡
- 불필요한 리소스 로딩
- 과도한 의존성
- 라이브러리, 프레임워크 의존성 증가
- 렌더링 차단 요소
- 최적화 방법
- JavaScript 코드 분할
- 코드 스플리팅: Webpack과 같은 도구를 사용해서 필요한 코드만 로드한다.
- Lazy Loading: 페이지에 필요하지 않은 스크립트는 지연 로드한다.
- 비동기 스크립트 로딩
- defer 및 async 사용: 스크립트를 비동기적으로 로드해서 초기 렌더링 방해 방지한다.
- 메인 스레드 작업 최소화
- 작업 최적화: 메인 스레드에서 수행하는 작업을 최소화하고, 필요할 때만 실행한다.
- JavaScript 코드 분할
TBT (Total Blocking Time)
TBT는 페이지가 마우스 클릭, 화면 탭, 키보드 입력과 같은 사용자 입력에 응답하지 못하도록 차단된 총 시간을 측정하는 성능 지표이다.
- FCP와 TTI 사이의 모든 50ms 이상 실행되는 작업의 차단 부분을 더해서 계산된다.
-
양호: 150ms 이하 개선 필요: 150ms ~ 300ms 나쁨: 300ms 이상 - 측정값이 나쁨으로 나오는 원인
- 긴 JavaScript 작업
- 메인 스레드 점유 시간 증가
- 불필요한 JavaScript 실행
- 불필요한 코드 실행
- 과도한 계산 작업
- 메인 스레드 부하 증가
- 긴 JavaScript 작업
- 최적화 방법
- 메인 스레드 작업 최적화
- 작업 분할: 긴 작업을 나누어 메인 스레드가 한 번에 처리해야 하는 작업의 양을 줄인다.
- Web Worker 사용: 비동기적으로 실행할 수 있는 작업을 Web Worker라는 별도의 스레드에서 처리해서 메인 스레드의 부하를 줄이고, 사용자 인터페이스가 차단되지 않도록 한다.
- 비동기 로딩
- 비동기 스크립트: 스크립트를 비동기적으로 로드해서 메인 스레드가 차단되는 시간을 최소화한다.
- 성능 모니터링
- 성능 분석 도구 사용: Lighthouse, WebPageTest 같은 도구를 사용해서 웹 페이짖의 차단 시간을 분석하고 개선한다.
- 메인 스레드 작업 최적화
각 지표 간의 연관성
지표 | 정의 | 연관성 |
---|---|---|
FCP (First Contentful Paint) | 사용자가 페이지를 요청한 후, 첫 번째 콘텐츠가 화면에 렌더링되는 시간 | FCP는 LCP와 연결되어 있고 초기 로딩 경험을 평가하는 데 중요한 역할을 한다. FCP가 빠르면 사용자는 페이지가 로드되고 있음을 더 빨리 인식할 수 있다. |
LCP (Largest Contentful Paint) | 페이지에서 가장 큰 콘텐츠 요소가 렌더링되는 시간 | LCP는 FCP와 TTI와 밀접한 관련이 있다. LCP가 빠르면 사용자가 페이지의 주요 콘텐츠를 더 빨리 볼 수 있고 TTI에 긍정적인 영향을 미친다. |
INP (Interaction to Next Paint) | 사용자가 페이지와 상호작용한 후, 다음 화면 변화가 나타날 때까지의 시간 | INP는 TTI와 TBT와 관련이 있다. INP가 짧으면 사용자가 상호작용 후 빠르게 반응을 받을 수 있고 TTI에 긍정적인 영향을 미친다. TBT가 길면 INP가 증가할 수 있다. |
CLS (Cumulative Layout Shift) | 페이지 로딩 중 발생하는 레이아웃 이동의 총합 | CLS는 FCP와 LCP와 관련이 있다. 레이아웃 이동이 많으면 FCP와 LCP가 부정적인 영향을 받을 수 있다. |
TTFB (Time to First Byte) | 사용자가 요청한 페이지의 첫 번째 바이트가 서버에서 전송되기까지 걸리는 시간 | TTFB는 FCP와 LCP에 영향을 미친다. TTFB가 짧으면 FCP와 LCP가 빨라질 가능성이 높다. |
TTI (Time to Interactive) | 페이지가 완전히 로드되고, 사용자와 상호작용할 수 있는 상태가 되는 데 걸리는 시간 | TTI는 FCP, LCP, INP와 관련이 있다. TTI가 짧으면 사용자가 페이지와 상호작용할 수 있는 시간이 빨라지고 FCP와 LCP의 성능에도 긍정적인 영향을 미친다. |
TBT (Total Blocking Time) | 페이지 로드 중 메인 스레드가 차단되어 사용자 상호작용을 처리할 수 없는 총 시간 | TBT는 TTI와 INP와 밀접한 관련이 있다. TBT가 길면 TTI가 증가하고, INP가 느려질 수 있다. |
번들 사이즈 최적화로 Web Vital 개선
1. 코드 분할 (Code Splitting)
큰 코드 덩어리를 여러 개의 작은 chunk 단위로 쪼개어 초기 로딩 시 필요한 코드만 로드하고,
사용자의 행동에 따라 필요한 chunk를 동적으로 로드할 수 있다.
- MPA (Multi Page Application)
Webpack config에서
entry point
를 설정해서 각 페이지별 번들을 생성할 수 있다.
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
- SPA (Single Page Application, React)
React.lazy()
와React.Suspense
를 활용해서 컴포넌트 단위로 코드 분할을 수행할 수 있다.
const Home = lazy(() => import('./pages/Home/Home'));
const Search = lazy(() => import('./pages/Search/Search'));
const App = () => {
return (
<Suspense fallback={<div>로딩중입니다.</div>}>
<Router>
<NavBar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/search" element={<Search />} />
</Routes>
</Router>
</Suspense>
);
};
2. 중복 청크 제거
여러 chunk에서 사용되는 공통 모듈을 별도의 chunk로 분리해서 중복 로딩을 방지할 수 있다.
splitChunks
옵션을 활용해서 중복된 chunk를 제거할 수 있다.
optimization: {
splitChunks: {
chunks: 'all', // 모든 chunk에 대해 splitChunks를 적용
minSize: 100 // 최소 chunk 크기를 설정 (default: 20000)
}
}
3. 트리 쉐이킹 (Tree Shaking)
사용되지 않는 코드 (dead code)를 제거해서 번들 크기를 줄일 수 있다.
-
100% ESM (ECMAScript Module)으로 작성된 코드는 프로덕션 모드 빌드 시 자동으로 트리 쉐이킹된다.
sideEffects
속성을 사용해서 사이드 이펙트가 있는 파일들을 명시적으로 지정할 수 있다.{ "name": "dongsup", "sideEffects": [ "./src/it-has-side-effect.js", "*.css" ] }
-
불필요한 코드가 번들에 남아 있지 않도록 Re-export를 활용해서 사용하지 않는 함수를 제거 할 수 있다.
// utils/index.js export { A } from './A'; export { B } from './B'; export { C } from './C';
-
런타임에 평가되는 조건문 대신 컴파일 타임에 평가되는 조건문을 사용하면 프로덕션 모드 빌드 시 개발 모드에 해당하는 코드를 제거할 수 있다.
// Bad const isDevelopment = process.env.NODE_ENV === 'development'; if (isDevelopment) banana(); // Good if (process.env.NODE_ENV === 'development') banana();
성능 측정 하는 법
web-vitals library
실제 필드 데이터를 측정하고자 할 때 유용하게 사용될 수 있는 JS 라이브러리로
Google Analytics와도 연동이 가능하다.
import {onLCP, onFID, onCLS} from 'web-vitals';
onCLS(console.log);
onFID(console.log);
onLCP(console.log);
Lighthouse
가장 대표적으로 사용하는 도구로는 크롬 개발자 도구에서 제공하는 Lighthouse가 있다. Lighthouse는 웹페이지 품질을 측정하기 위한 오픈소스 자동화 도구로 구글 라이트 하우스는 구글 크롬 DevTools와 크롬 확장 프로그램을 통해 활용할 수 있다.
크롬 개발자 도구
Lighthouse와 별개로 Chrome 개발자 도구의 퍼포먼스 탭과 네트워크 탭 그리고 메모리 탭을 이용하는 방법이 있다.
퍼포먼스 탭
퍼포먼스 탭에서는 직접 원하는 구간을 녹화함으로써 네트워크, 렌더링 그리고 메모리 전반에 관한 사항을 확인할 수 있다.
메모리 탭
메모리 탭을 이용하면 현재 메모리의 사용률을 확인할 수 있고 메모리 탭에서 Heap Snapshot을 찍고, 각 Snapshot 간의 차이를 비교해서 어느 항목에서 메모리 누수가 발생했는지 찾을 수 있다.
네트워크 탭
네트워크 탭을 통해서 에셋을 불러오거나 네트워크 요청이 처리되는데 얼마나 시간이 걸리는지 확인할 수 있다. 기본적으로 프리셋이 제공되고 있으며 서비스에서 목표로 하는 네트워크 환경에 대한 설정을 커스터마이징할 수 있다.
리액트 프로파일러
리액트로 만들어진 앱의 경우에 사용할 수 있. 리액트 프로파일러를 이용해 컴포넌트별 렌더링 시간을 파악할 수 있고, 사용자의 인터렉션에 대한 변화를 추적할 수 있다.
모니터링 도구
개발 도중이 아니라 실시간 서비스 중에도 성능을 측정하는 방법이다. 네트워크 로딩 속도, AJAX 요청 속도 그리고 자바스크립트 에러 등을 모니터링할 수 있다. 예시로는 제니퍼 프론트(Jennifer Front)와 뉴렐릭(Newrelic)이 있다.
reference
댓글남기기