본문 바로가기

FrontEnd

프론트엔드 성능 최적화 가이드 1장 스터디

반응형

프론트엔드 성능 최적화 가이드의 1장을 기반으로 진행한 스터디 입니다.

 

프론트엔드 성능 최적화 가이드

웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로드되면 금세 ‘뒤로 가기’ 버튼에 손이 갑니다. 구글의 연구에 따르면 페이지 표시 시간이 1초에서 3초

blog.insightbook.co.kr

성능 최적화 개요

성능 최적화가 중요한 이유

  • 구글 : 성능이 곧 매출
    • 성능이 저하되면 사용자가 떠나고 매출이 감소한다

페이지 표시 시간 증가에 따른 이탈율

즉, 성능을 최적화하면 더 나은 UX, 높은 가입율과 전환율, 낮은 이탈율을 확보하여 더 많은 수익을 창출할 수 있음

 

성능 최적화는 어떻게 이루어질까

웹 성능 결정 요소는 로딩 성능과 렌더링 성능으로 나눌 수 있음

로딩 성능

웹 페이지와 웹 페이지 표시에 필요한 기타 리소스를 다운로드할 때의 성능

  • 이미지, HTML, CSS, JS, 비디오 등
  • 파일이 너무 크면 다운로드가 오래 걸려 사용자에게 웹 페이지가 느리게 표시됨

개선 방법

  • 다운로드 대상 리소스 수 줄이기
  • 다운로드 개상 리소스 용량 줄이기
  • 지연 로딩(필요할 때 다운로드 - 코드 스플리팅)
  • 우선순위 이용(중요한 리소스 먼저 다운로드

렌더링 성능

다운로드한 리소스를 가지고 화면을 그릴 때의 성능

(코드를 실행하여 화면에 보여주는 과정)

  • 자바스크립트 코드의 실행
    • 얼마나 효율적으로 코드를 작성했느냐

개선 방법

  • 케이스 바이 케이스
    • 서비스 유형에 따라
    • 브라우저 동작 원리에 따라
    • 프레임워크에 따라

1장 블로그 서비스 최적화하기


실습 배경지식

1장에서는 다음과 같은 내용을 다룹니다

 

로딩 성능 최적화

  • 이미지 사이즈 최적화
  • 코드 분할 & 지연 로딩
  • 텍스트 압축

렌더링 성능 최적화

  • 병목 코드 최적화

프로젝트 주소

https://github.com/performance-lecture/lecture-1.git

실습 내용 소개

블로그 글 목록과 상세 페이지로 구성됨.

각 페이지는 최적화 관점에서 컴포넌트의 특성이 약간 다름

  • 데이터, 리소스 페치가 많은 컴포넌트가 있을 수 있음
  • 컨텐츠가 많은 컴포넌트가 있을 수 있음

이미지 사이즈 최적화

  • 적절한 이미지 사이즈를 사용해 용량과 화질의 적절한 타협점을 찾음

코드 분할

  • 용량이 크거나, 당장 혹은 반드시 필요하지 않은 코드를 나중에 로딩
  • 아니면 다운로드 받아둔 후, 나중에 코드를 해석하도록 할 수 있음

텍스트 압축

  • HTML, CSS, JS등의 리소스를 다운로드 전에 서버에서 미리 압축

병목 코드 최적화

  • 실행시간을 잡아먹는 코드의 최적화

분석 툴 소개

크롬 개발자 도구

네트워크 패널

  • 현재 웹 페이지에서 발생하는 모든 네트워크 트래픽을 상세하게 알려줌
    • 리소스 로드 시점
    • 리소스 크기

퍼포먼스 패널

  • 웹 페이지가 로드될 때 실행되는 모든 작업을 보여줌
    • 리소스 로드 타이밍
    • 브라우저의 메인 스레드에서 실행되는 자바스크립트를 차트 형태로 볼 수 있음
    • 어떤 자바스크립트 코드가 느린지 확인할 수 있음

크롬 개발자 도구 Lighthouse 패널

  • 웹사이트의 성능을 측정하고 개선 방향을 제시해주는 자동화 툴
  • 개선 가이드를 확인하여 어떤 부분을 중점적으로 분석하고 최적화해야 하는지 알 수 있음

webpack-bundle-analyzer

  • 웹팩을 통해 번들링 된 파일이 어떤 코드, 어떤 라이브러리를 담고 있는지 보여줌
    • 불필요한 코드 식별, 번들 파일에서의 코드 비중 식별 
    • 용량이 큰 외부 라이브러리가 어떤 컴포넌트에서만 특정적으로 사용되는지 확인 후 코드 스플리팅
    • 경로 별 코드 스플리팅

Lighthouse 툴을 이용한 페이지 검사

Mode

  • Navigation : Lighthouse의 기본 값으로, 초기 페이지 로딩 시 발생하는 성능 문제를 분석
  • Timespan : 사용자가 정의한 시간 동안 발생한 성능 문제 분석
  • Snapshot : 현재 상태의 성능 문제를 분석

Categories

  • Performance : 웸 페이지 로딩 과정에서 발생하는 성능 문제를 분석
  • Accessibility : 서비스의 사용자 접근성 문제를 분석
  • Best practice : 웹사이트의 보안 측면과 웹 개발의 최신 표준에 중점을 두고 분석
  • SEO : 검색 엔진에서 얼마나 잘 크롤링되고 검색 결과에 표시되는지 분석
  • Progressive Web App : 서비스 워커와 오프라인 동작 등  PWA와 관련한 문제를 분석

실습 환경 설정

  • Mode는 기본 값인 Navigation 이용
  • Categories 항목에서 원하는 검사 주제 선택
    • 이번에는 Performance만 선택
  • Device는 Desktop
    • Mobile을 설정하면 좀 더 느린 CPU와 네트워크 환경 사용

검사 결과 : Web Vitals

파란색 Analyze page load 버튼 클릭 시 결과 확인 가능

 

  • 종합 성능 점수 : 아래 6개의 지표(metrics)에 가중치를 적용해 평균 낸 점수
    • 이러한 6개의 지표들을 웹 바이탈(Web Vitals)라 부름 
    • 47
  • First Contentful Paint(FCP)
    • 페이지가 로드될 때 브라우저가 DOM 콘텐츠의 첫번째 부분을 렌더링 하는데 걸리는 시간에 관한 지표
    • 10% 가중치
    • 1.4s
  •  Speed Index(SI)
    • 페이지 로드 중에 콘텐츠가 시각적으로 표시되는 속도를 나타내는 지표
      • 빨리 더 많은 컨텐츠를 보여줄 수록 가산점을 받음
    • 10% 가중치
    • 2.5s

A가 빨리 더 많이 보여주므로 더 높은 점수를 받음 (낮은 로딩시간)

  • Largest Contentful Paint(FCP)
    • 페이지가 로드될 때 화면 내에 있는 가장 큰 이미지나 텍스트 요소가 렌더링되기까지 걸리는 시간을 나타내는 지표
    • 25% 가중치
    • 2.1s
  • Time to Interactive(TTI)
    • 페이지와 상호 작용(클릭 또는 키보드 누름과 같은 사용자 입력) 가능한 시점까지 걸리는 시간
    • 10% 가중치
    • 3.5s
  • Total Blockint Time(TBT)
    • 페이지가 클릭, 키보드 입력 등의 사용자 입력에 응답하지 않도록 차단된 시간을 종합한 지표
    • FCP와 TTI 사이의 시간 측정
      • 메인 스레드를 독점하여 다른 동작을 방해하는 작업에 걸린 시간을 총합
    • 30% 가중치
  • Cumulative Layout Shift(CLS)
    • 페이지 로드 과정에서 발생하는 예기치 못한 레이아웃 이동을 측정한 지표
      • 레이아웃 이동이란 화면상에서 요소의 위치나 크기가 순간적으로 변하는 것을 의미
    • 15% 가중치
    • 0.061

검사 결과 : Opportunities, Diagnostics

문제점과 해결 방안, 그리고 문제를 해결함으로써 얻을 수 있는 이점을 보여줌

 

Opportunities 섹션

  • 로드 속도에 도움이 되는 제안

Diagnostics 섹션

  • 로드 속도와 관련이 없지만 성능과 관련된 기타 정보

검사 결과 : 검사 환경

가장 하단의 정보

  • CPU throttling : CPU 성능 제한
    • 1x : 제한 없음. 데스크탑
    • 4x : 모바일 선택시.
  • Network throttling : 네트워크 속도 제한

보통 lighthouse의 환경은 일반적인 환경보다 성능이 낮은 환경을 에뮬레이션 함

이미지 사이즈 최적화

비효율적인 이미지 찾기

실습 커밋 : https://github.com/i0boy/frontend-performance-optimization-1/commit/23f59db93015670c9a8450a7bf161a2ad22526ca

Opportunities 섹션의 Properly size images > 적절한 사이즈로 이미지 사용하기

이미지 영역이 120x120px 고정 크기인데 1200 x 1200px을 내려주고 있음.

dpi를 고려해 240x240px 정도만 내려주면 된다.

(너비 기준으로 두배 정도 큰 이미지를 사용하는 것이 적절합니다.)

자체적으로 가지고 있는 정적 이미지 : 편집 툴 이용

API로 받아오는 이미지 : 이미지 CDN 이용

이미지 CDN

Content Delivery Network란 물리적 거리의 한계를 극복하기 위해 소비자(사용자)와 가까운 곳에 콘텐츠 서버를 두는 기술

미국에 있는 서버를 미리 한국으로 복사해 두고 사용자가 이미지를 다운로드 시 미국이 아닌 한국에서 다운로드하도록 함

이미지 CDN은 이미지에 특화된 CDN

CDN 기능(물리적 근접성) + 특정 형태로 가공하는 기능

  • 이미지 사이즈 줄이기
  • 포맷 변경

이미지 사이즈 줄이기 / 포맷 변경

일반적인 이미지 CDN에서 제공하는 주소 포맷

쿼리스트링으로 가져올 이미지 주소 또는 이름, 변경하고자 하는 형태 명시

이런 이미지 CDN을 자체 개발해서 쓰거나, Imgix와 같은 CDN 솔루션 사용 가능

Unsplash 또한 일종의 이미지 CDN 역할을 함

Unsplash는 고해상도 이미지를 무료로 제공하는 이미지 플랫폼 서비스

병목 코드 최적화

실습 내용 : https://github.com/i0boy/frontend-performance-optimization-1/commit/a585d1f4640de422f74f1d160a566c5caf6b06f5

Performance 패널 살펴보기

코드 실행은 로딩과 관련된 내용이 아님

Lighthouse의 Diagnostics 섹션을 살펴봄

'Reduce Javascript execution time' 항목을 살펴봄

1.chunk.js 파일의 실행이 오래걸림

느린 작업을 식별하기 위해 Performance 탭을 살펴봄

Lighthouse에서 이동 가능

View Original Trace로 이동 시 상세 정보가 누락된 경우도 있음.

  • 퍼포먼스 패널로 이동해서 새로고침으로 분석

서비스 상태를 좀 더 상세히 보려면 네트워크 설정을 Fast 3G로 설정

퍼포먼스 패널을 이용한 분석 결과

1. CPU 차트, 네트워크 차트, 스크린샷

  • CPU 차트
    • CPU가 시간에 따라 어떤 작업에 리소스를 사용하고 있는지 비율을 나타냄 
      • 자바스크립트 실행은 노란색
      • 렌더링 / 레이아웃 작업은 보라색
      • 페인팅 작업은 초록색
      • 기타 시스템 작업은 회색
    • 빨간색 선을 통해 병목 지점 식별 가능
      • 특정 작업이 메인 스레드를 오래 붙잡고 있음을 의미
  • 네트워크 차트
    • CPU 차트 밑에 막대 형태
    • 대략적인 네트워크 상태 표시
      • 위쪽의 진한 막대는 우선순위가 높은 네트워크 리소스
      • 아래쪽의 옅은 막대는 우선순위가 낮은 네트워크 리소스
  • 스크린샷
    • 맨 아래 스크린샷 리스트
    • 서비스가 로드되는 과정을 보여줌

 

2. 네트워크 타임라인

  • 네트워크 패널과 유사
  • 서비스 로드 과정에서의 네트워크 요청을 시간 순서에 따라 보여줌

네트워크 타임라인

각 네트워크 요청 막대가 의미하는 것

  • 왼쪽 회색 선 : 초기 연결 시간
  • 막대의 옅은 색 영역 : 요청을 보낸 시점부터 응답을 기다리는 시점까지의 시간(TTFB, time to first byte)
  • 막대의 짙은 색 영역 : 콘텐츠 다운로드 시간
  • 오른쪽 회색 선 : 해당 요청에 대한 메인스레드의 작업 시간

3. Frames, Timings, Main

 

Frames 섹션은 화면의 변화가 있을 때마다 스크린샷을 찍어 보여줌

Timings 섹션은 웹 바이탈과 User Timing API를 통해 기록된 정보를 보여줌

FP, DCL과 L이벤트 발생 시점도 보여줌

자세한 사항은 https://www.debugbear.com/blog/devtools-performance 참조

리액트 자체적으로도 User Timing API를 제공했으나 17버전부터 제공하지 않음

Main 섹션은 브라우저의 메인 스레드에서 실행되는 작업을 플레임 차트(Flame chart)로 보여줌

  • 플레임 차트 : 소프트웨어 작업(스택)을 손쉽게 추적하기 위해 개발된 계층적 데이터 시각화 방법
    • 크롬 개발자 도구는 위가 하위 작업(스택아래), 아래가 상위 작업(스택탑)

병목 작업 파악 가능

4. 하단 탭

전체 또는 선택된 영역에 대한 상세 내용 확인 가능

  • Summary 탭
    • 선택 영역에서 발생한 작업 시간의 총 합과 각 작업의 비중
  • Buttom Up 탭
    • 최하위 작업부터 상위 작업까지 역순으로 보여줌
      • 상단이 스택 바텀
  • Call Tree 탭
    • 바텀업과 비슷한데 스택 대신 작업의 순서 및 의존성을 고려해서 계층별로 보여줌
    • (책의 설명이 잘못된 것으로 추정)
  • Event Log 탭
    • 발생한 이벤트를 보여줌
    • Loading
    • Experience Scripting
    • Rendering
      • 렌더트리와 레이아웃
    • Painting
      • 컴포지트 + 페인트

페이지 로드 과정 살펴보기

페이지가 처음 로드되는 시점 살펴보기

0.chunk.js파일의 처리 시간이 길다는 것을 알 수 있음. 즉 용량이 큼 (4.2MB)

HTML 다운로드 시점을 보면 Main 스레드에서 Parse HTML 작업을 처리하고 있음을 알 수 있음

0.chunk.js 파일의 다운로드가 끝나고 JS가 실행됨

이상한 점 : ArthcieList를 렌더링 하는 데 이미 데이터(articles)가 다운로드 된 이후임에도 1.4초가 걸림

메인스레드의 호출 스택을 따라가다 보면 Article이라는 작업에서 병목 작업을 찾을 수 있음

위 작업은 원래 하나인데 메모리를 너무 많이 먹어 Minor GC가 실행되는 관계로 여러 개로 쪼개져 보임

 

함수 최적화 방법

  • 알고리즘 최적화
  • 작업 양 줄이기 / 나누어 처리하기

최적화 전 후 비교

TTI와 TBT가 줄어 Metric 개선 효과를 볼 수 있음

코드 분할 & 지연 로딩

실습 내용 : https://github.com/i0boy/frontend-performance-optimization-1/commit/475fe8fe306afa70d2297cce8b1596d9c6d090d7

번들 파일 분석

네트워크 탭에서 다운로드가 오래 걸리는 번들 식별

webpack-bundle-analyzer

전체 소스에서 해당 번들이 얼마나 큰 크기를 차지하며 어떤 의존성으로 구성되어 있는지 트리맵으로 시각화해주는 라이브러리

cra에서 사용하는 방법은 아래 문서 참조

https://create-react-app.dev/docs/analyzing-the-bundle-size/

 

Analyzing the Bundle Size | Create React App

Source map explorer analyzes

create-react-app.dev

해당 책에서는 cra-bundle-analyzer 라이브러리를 사용해 진행

우리 패키지를 분석해보면 외부 패키지의 refractor 친구가 매우 크다는 것을 알 수 있음

번들 파일의 이름(번호와 해시)은 코드가 수정되거나 빌드될 때마다 달라질 수 있음

오른쪽 상단을 보면 파란색 블록이 보이고, 이름으로 유추하면 사용자가 작성한 서비스 코드임을 알 수 있음

즉 사용자가 직접 작성한 코드는 main.머시기.chunk.js

외부 모듈은 번호.머시기.chunk.js가 됨

번들 최적화

외부 라이브러리인 refractor와 react-dom의 크기가 큼

  • react-dom은 리액트를 위한 코드라 어쩔 수 없음
  • refractor를 package-lock.json에서 찾으면 어떤 패키지가 해당 패키지를 사용하는지 알 수 있음

 

react-syntax-highlighter라는 친구를 사용하는 컴포넌트를 코드 스플리팅 하면 번들 크기를 줄일 기회를 얻을 수 있음

코드 스플리팅

페이지 / 컴포넌트 별 코드를 분리하여 불필요한 코드를 로드하지 않도록 함

  • 하나의 번들 파일을 여러 개의 파일로 쪼갤 수 있음
  • 분할된 코드는 사용자가 서비스를 이용하는 중 해당 코드가 필요해지는 시점에 로드되어 실행
    • 이를 지연 로딩이라고 함

공통모듈이 많고 그 모듈의 크기가 큰 경우엔 모듈 별로 코드 스플리팅을 할 수도 있음

핵심은 불필요한 코드 없이, 코드 중복 없이 적절한 사이즈의 코드를 적절한 타이밍에 로드하는 것

 

리액트의 경우 lazy와 suspense를 활용함

그 외에는 다이나믹 임포트 활용

코드 스플리팅 후 번들 분석

라우트 별로 코드 스플리팅을 적용 후 번들 분석 결과

컴포넌트와 외부 패키지가 따로 번들되는 것은 CRA 기본 웹팩 설정 때문임

  • 0.chunk.js : ListPage에서 사용하는 외부 패키지를 모아 둔 번들 파일(axios)
  • 3.chunk.js : ViewPage에서 사용하는 외부 패키지를 모아 둔 번들 파일(react-syntax-highliter)
  • 4.chunk.js : 리액트 공통 패키지를 모아둔 번들 파일(react-dom 등)
  • 5.chunk.js : ListPage 컴포넌트 번들 파일
  • 6.chunk.js : ViewPage 컴포넌트 번들 파일

용량 : 4메가에서 1.9메가로 감소.

  • 프로덕션 빌드를 적용하면 더 많이 감소하고
  • 네트워크 쓰로틀링(현재 3G)을 감안하면 실제 환경에서는 더 빨리 다운도드 될 것임

네트워크 탭에서 지연 로딩 확인 가능

텍스트 압축

실습 내용 : https://github.com/i0boy/frontend-performance-optimization-1/commit/b49c0d93cfe8f6af919e068b32bd875eae7f7de9

 

production 환경과 development 환경의 가장 큰 차이는 난독화와 경량화 등 추가 최적화 작업

 

ListPage(목록 페이지)대신 ViewPage(상세조회 페이지)에 Lighthouse를 사용해보면 결과가 다름

  • 큰 패키지 번들(이전에 스플리팅함)
  • 글 내용이 많음

그럼에도 짚고 넘어갈 부분이 있음 : Opportunities 섹션의 Enable text compression임

목록페이지의 경우 번들 파일이 작아 압축을 유의미한 작업이라고 판단하지 않고 보여주지 않을 수 있음

텍스트 압축이란

텍스트 기반 압축 알고리즘을 적용하여 리소스 전송을 빠르게

인기 있는 알고리즘은 Gzip과 Deflate가 있지만 주로 Gzip을 쓴다는 것만 알면 됨

압축 여부는 HTTP 헤더에서 Content-Encoding: gzip을 찾아보면 됨.

텍스트 압축은 어떤 웹 서버를 사용하느냐에 따라 다름. 실습 기준은 serve 라이브러리 사용 시 기준임

vercel, nginx, netlify 등의 웹 서버 마다 다른 설정 방법이 존재함.

 

 

반응형