Lighthouse 성능 분석 (FCP / LCP / TBT)

룸잉·2025년 1월 16일
1

thumbnail


0️⃣ 로컬 환경과 배포된 서비스 간의 성능 차이가 발생하는 이유

Lighthouse로 성능을 체크해보면 로컬 환경과 배포된 서비스에서 성능 차이가 꽤 많이 발생한다.

보통은 배포 서비스에서 성능이 더 좋게 나오는데 그 이유는 번들러가 코드를 최적화하기 때문이다.
번들링 과정에서 코드 압축, Tree Shaking 같은 최적화 작업이 일어난다. 이를 통해 불필요한 코드가 제거되고, 번들 크기가 줄어들며, 브라우저가 더 효율적으로 로딩되도록 환경이 개선된다.

반면 로컬 환경에서는 디버깅 편의성을 위해 코드 최적화가 생략되거나 제한적으로 적용되기 때문에, 같은 코드로 구현되어 있더라도 로컬 환경과 배포 서비스 사이에 성능 차이가 발생할 수 있다.


1️⃣ FCP / LCP

✏️ 측정 시점

  • FCP (First Contentful Paint)
    : 브라우저가 요청을 보낸 시점부터 DOM의 첫 번째 콘텐츠가 화면에 렌더링되는 시점

  • LCP (Largest Contentful Paint)
    : 브라우저가 요청을 보낸 시점부터 가장 큰 콘텐츠 요소가 화면에 렌더링되는 시점


✏️ FCP가 줄어들면 LCP가 줄어들까 ?

DOM의 첫 번째 콘텐츠가 화면에 렌더링되는 시점(FCP)이 개선된다면 가장 큰 콘텐츠 요소가 화면에 렌더링되는 시점(LCP) 자체를 개선하지 않더라도, 초반 렌더링 속도가 개선됨에 따라 가장 큰 콘텐츠 요소가 렌더링되는 시점에도 영향을 미칠 것이다.

아래 그림을 보면 이해하기 쉽다.

핵심은 FCP와 LCP를 따로 생각하는 것이 아니라, 아래와 같이 연결되어 있는 모습으로 생각해보자.

FCP와 LCP

FCP가 줄면, LCP가 줄어들 수 밖에 없다.

그럼에도 예외는 있다.
FCP와 관계없이 LCP 자체에만 문제가 있는 경우라면, FCP를 최적화해도 LCP에 큰 영향을 미치지는 않을 것이다. (그렇지만 이런 경우는 많지 않다고 생각한다.)


2️⃣ TBT

✏️ 측정 시점

  • TBT (Total Blocking Time)
    : 마우스 클릭, 화면 탭, 또는 키보드 누르기와 같은 사용자 입력에 페이지가 응답하지 못하는 총 시간
    : DOM의 첫 번째 콘텐츠가 화면에 렌더링되는 시점(FCP)부터 상호작용 시작 시점(TTI/ Time To Interective)

3️⃣ React와 Next.js 프로젝트의 성능 지표 차이

✏️ React와 Next.js의 FCP/LCP와 TBT 비교

  • React로 구현한 프로젝트는 FCP, LCP가 느리고 TBT가 빠르다.
  • Next.js로 구현한 프로젝트는 FCP, LCP가 빠르고 TBT가 느리다.

위와 같은 현상이 나타나는 이유가 뭘까?

React와 Next.js의 초기 렌더링 방식에 대해 생각해보자.
React는 CSR 방식, Next.js는 SSR 방식으로 초기 렌더링이 진행된다.


✏️ CSR vs SSR

▶️ CSR (Client Side Rendering)

: 첫 화면 렌더링 시, 클라이언트 단에서 모든 JS 번들을 받아오고 이를 처리하기 전까지 아무런 화면도 보여주지 않는다.

=> 초기 렌더링까지 비교적 오랜 시간이 걸리며, FCP와 LCP가 느리다.
=> 하지만 초기에 모든 JS 번들을 불러오기 때문에, TBT는 빠르다.


▶️ SSR (Server Side Rendering)

: JS 번들을 처리하기 전에, 첫 화면에 필요한 HTML을 먼저 화면에 띄워준다.
이 시점에서 HTML은 웹 화면을 보여주는 HTML일 뿐, 이벤트 리스너를 포함하고 있지 않다.
이후, 하이드레이션 과정을 통해 JS(이벤트 리스너 포함)를 연결하여 HTML을 상호작용 가능한 상태로 전환한다.

=> 초기 렌더링까지 비교적 시간이 적게 걸리며, FCP와 LCP가 빠르다.
=> 하지만 초기에 모든 JS 번들을 불러오지 않기 때문에, TBT와 화면 간 이동 속도가 느리다.


✏️ Next.js TBT 개선

Next.js에서는 동적인 화면을 만들기 위해 실행되는 하이드레이션 시간이 TBT 시간이 된다.
결국, TBT를 개선하기 위해서는 하이드레이션 시간을 줄이면 된다.

정적인 화면에서는 하이드레이션 과정이 불필요하기 때문에 모든 컴포넌트에서 하이드레이션이 수행될 필요는 없다. 이를 위해 하이드레이션을 수행하지 않는 서버 컴포넌트가 등장했다.
인터랙티브한 화면을 구현해야 한다면 use client를 활용하여 클라이언트 컴포넌트로 만들어주면 된다.

빠른 초기 렌더링이라는 강점은 살리면서, 서버 컴포넌트를 적극적으로 활용하여 페이지 간 이동이 자연스럽고 TBT와 같은 성능 저하가 느껴지지 않는다면 Next.js를 효과적으로 활용했다고 할 수 있다.

▶️ 인터렉션이 많은 서비스라면 Next.js를 사용하는게 큰 의미가 있을까?

인터랙션이 많다는 것은 클라이언트 컴포넌트의 비중이 높아지고 하이드레이션이 빈번하게 발생한다는 뜻이다. 이러한 경우, 무거운 React를 사용하는 것과 큰 차이가 없어진다.

▶️ 인터렉션을 구현하려면 클라이언트 컴포넌트가 필요한 이유

이벤트 핸들러는 윈도우 객체에 속한다. 이 때 윈도우 객체는 브라우저에만 있기 때문에, 서버에서 처리할 수 없다.
따라서 인터렉션을 구현하기 위해서는 클라이언트 컴포넌트가 필요하다.

0개의 댓글

관련 채용 정보