LCP(Largest Contentful Paint) 최적화하기


원문: "Optimize Largest Contentful Paint" https://web.dev/optimize-lcp/- Houssein Djirdeh 라이선스: CC BY 4.0

주요 컨텐츠를 더 빠르게 렌더링하는 방법

사용자 경험을 저하시키는 한 가지 요인은 사용자가 화면에 렌더링된 콘텐츠를 보는데 걸리는 시간이다. First Contentful Paint(FCP)는 초기 DOM 콘텐츠를 렌더링하는데 걸리는 시간을 측정하지만, 가장 큰 콘텐츠(보통은 중요한 콘텐츠일수록 크기가 크다)를 페이지에 렌더링하는데 걸리는 시간을 측정하지는 않는다.

Largest Contentful Paint(LCP)는 Core Web Vitals의 지표이며 뷰포트에서 가장 큰 콘텐츠 엘리먼트가 나타날 때 측정한다. 페이지의 주요 내용이 화면에 렌더링이 완료되는 시기를 결정하는데 사용된다.

Good LCP values are 2.5 seconds, poor values are greater than 4.0 seconds and anything in between needs improvement

LCP가 저하되는 일반적인 원인은 다음과 같다.

느린 서버 응답 시간

브라우저가 서버에서 콘텐츠를 받는데 걸리는 시간이 오래 걸릴수록 화면에 렌더링하는데 시간이 더 오래 걸린다. 더 빠른 서버 응답 시간은 LCP를 포함해서 모든 페이지 로딩 지표를 직접적으로 향상시킨다.

무엇보다도 먼저 서버가 콘텐츠를 처리하는 방법과 위치를 개선해야한다. 서버 응답 시간을 측정하려면 Time to First Byte(TTFB)를 사용한다. 다양한 방법으로 TTFB를 개선할 수 있다.

  • 서버를 최적화한다.
  • 사용자와 가까운 CDN을 사용한다.
  • 자원을 캐시한다.
  • HTML 페이지를 우선 캐시되도록 한다.
  • 서드파티 자원의 연결을 일찍한다.

서버를 최적화한다.

여러분의 서버가 완료되는데 아주 오래 걸리는 비싼 쿼리를 실행하고 있는가? 아니면 서버에서 페이지 콘텐츠를 보내는 프로세스를 지연시킬만한 복잡한 동작이 있는가? 서버 쪽 코드의 효율성을 분석하고 개선하는 것은 브라우저가 데이터를 받는 시간을 직접적으로 개선한다.

브라우저의 요청에 단순히 정적 페이지를 즉시 제공하는 대신, 많은 서버 측 웹 프레임워크가 웹 페이지를 동적으로 생성해야 한다. 즉, 브라우저가 요청할 때 이미 준비되어 완성된 HTML 파일을 보내기 보다는, 프레임워크가 페이지를 생성하는 로직을 실행해야 한다. 그 이유는 데이터베이스 쿼리의 결과가 지연되기 때문일 수도 있고, 컴포넌트가 UI 프레임워크(예> React)에 의해 마크업으로 생성되어야 하기 때문일 수도 있다. 서버에서 동작하는 많은 웹 프레임워크는 이 과정을 빠르게할 수 있는 성능 가이드를 제공한다.

더 많은 팁은 서버의 병목을 개선하기를 확인하라.

사용자와 가까운 CDN을 사용한다.

콘텐츠 전송 네트워크(CDN)은 다양한 위치에 분산된 서버들의 네트워크다. 웹 페이지의 콘텐츠가 서버 하나에서 호스팅되고 있다면, 사용자들의 브라우저 요청이 말 그대로 전 세계를 여행해야 하기 때문에 웹사이트는 지리적으로 멀리 떨어져 있는 사용자들에게 느리게 로딩될 것이다. CDN을 사용하여 사용자가 먼 곳의 서버에 대한 네트워크 요청을 절대 기다리지 않도록 한다..

자원을 캐시한다.

HTML이 정적이고 요청마다 달라질 필요가 없다면, 캐시하기는 불필요한 HTML 생성을 방지한다. 디스크에 생성된 HTML의 복사본을 저장하므로써 서버 측 캐시하기는 TTFB를 줄이고 리소스 사용량을 최소화할 수 있다.

툴체인에 따라서 서버 캐시를 적용하는 방법은 다양하다.

  • 애플리케이션 서버의 앞단에 설치되어 캐시된 콘텐츠를 제공하거나 캐시 서버로 동작하도록 리버스 프록시 (예> Varnish, nginx)를 설정한다.
  • 클라우드 제공자(Firebase, AWS, Azure)의 캐시 동작을 설정하고 관리한다.
  • 콘텐츠가 사용자에게 더 가까운 곳에 캐시되고 저장될 수 있도록 엣지 서버를 제공하는 CDN을 사용한다.

HTML 페이지를 우선 캐시되도록 한다.

서비스 워커는 브라우저 백그라운드에서 실행되고 서버로부터의 요청을 하지 않도록 할 수 있다. 이런 프로그래밍에 의한 캐시 제어 단계는 HTML 페이지 콘텐츠의 일부 혹은 모든 것을 캐시할 수 있도록 하며 콘텐츠가 변경될 때만 캐시를 업데이트할 수도 있다.

다음 차트는 이 방법을 사용한 사이트에서 LCP 지표가 어떻게 개선되는지 보여준다.

image 서비스 워커의 사용 유무에 따른 페이지 로딩에서 Largest Contentful Paint 분포도 - philipwalton.com

이 차트는 지난 28일 동안 단일 사이트에서 서비스 워커의 상태에 따라 분류된 LCP의 분포를 보여준다. HTML 페이지의 우선 캐시가 서비스 워커에 도입된 후(차트의 파란 영역) 페이지 로드가 얼마나 더 빠른 LCP 값을 가지는지 주목한다.

전체 혹은 일부 HTML 페이지 우선 캐시를 제공하는 기법에 대해서 더 많은 것을 배우려면, 서비스 워커을 사용한 더 작은 HTML 페이로드를 살펴보라.

서드파티 자원의 연결을 일찍한다.

또한 서드파티의 오리진에 대한 서버 요청은 특히 페이지에 중요한 콘텐츠를 표시하는데 사용되는 경우, LCP에 영향을 줄 수 있다. rel="preconnect"를 사용하여 페이지가 가능한 빨리 연결을 설정할 것임을 브라우저에게 알려준다.

<link rel="preconnect" href="https://example.com" />

더 빠른 DNS 룩업을 위해 dns-prefetch 를 사용할 수도 있다.

<link rel="dns-prefetch" href="https://example.com" />

두 힌트는 비록 다르게 동작하지만 preconnect를 지원하지 않는 브라우저의 폴백으로 dns-prefetch를 설정한다.

<head><link rel="preconnect" href="https://example.com" />
  <link rel="dns-prefetch" href="https://example.com" />
</head>

페이지 속도를 개선하기 위해 네트워크 연결을 조기에 설정하기를 읽고 더 배울 수 있다.

자바스크립트와 CSS의 렌더 블로킹

브라우저가 콘텐츠를 렌더링하려면, HTML 마크업을 파싱해서 DOM 트리로 만들어야 한다. HTML 파서는 외부 스타일시트(<link rel="stylesheet">)나 동기적인 자바스크립트 태그(<script src="main.js">)를 만나면 중단될 것이다.

스크립트와 스타일시트는 모두 FCP를 지연시키고 결과적으로 LCP를 지연시키는 렌더 블로킹 리소스다. 웹 페이지의 주요 컨텐츠를 빠르게 로딩하기 위해서 중요하지 않은 자바스크립트와 CSS는 지연시킨다.

CSS 블로킹 시간을 개선한다.

다음에 제시하는 것으로 최소한의 필수적인 CSS만 사이트의 렌더링을 블로킹하도록 한다.

  • CSS를 최소화한다.
  • 중요하지 않은 CSS를 지연시킨다.
  • 중요한 CSS는 내재한다.

CSS를 최소화한다.

CSS 파일은 읽기 쉽도록 하기 위해서 스페이스, 들여쓰기, 혹은 주석과 같은 문자들을 포함할 수 있다. 이런 문자들은 브라우저에게는 필요하지 않으며, 파일을 최소화하는 것으로 해당 문자가 제거될 수 있다. 결국 블로킹 CSS의 양을 줄이면 페이지의 주요 콘텐츠를 모두 렌더링되는 시간(LCP)을 항상 개선할 수 있다.

만약 모듈 번들러나 빌드 도구를 사용하고 있다면, 빌드 시마다 CSS파일을 최소화 하기 위해 적합한 플러그인을 포함시킨다.

image CP 개선 예: CSS를 최소화하기 전과 후

더 자세한 자료는 CSS를 최소화하는 가이드를 참고하라.

중요하지 않은 CSS를 지연시킨다.

여러분의 웹 페이지에서 사용되지 않는 CSS를 찾기 위해서 크롬 개발자 도구의 Coverage 탭을 사용한다.

Coverage tab in Chrome DevTools

다음은 최적화 방법이다.

  • 사용되지 않는 CSS를 전부 제거하거나 사이트의 별도 페이지에서 사용되는 경우 다른 스타일시트로 이동한다.
  • 초기 렌더링에 필요하지 않은 CSS의 경우는, loadCSS를 사용하여 파일을 비동기 방식으로 로드한다. 이 경우 rel="preload"onload를 활용한다.
<link rel="preload" href="stylesheet.css" as="style" onload="this.rel='stylesheet'" />

image LCP 개선 예: 중요하지 않은 CSS를 지연시키기 전과 후

더 자세한 내용은 중요하지 않은 CSS를 지연하기 가이드를 참고하라.

중요한 CSS는 내재한다.

CSS를 <head>에 직접 포함시켜 상단의 콘텐츠에서 사용되는 핵심경로의 CSS를 내재한다.

Critical CSS inlined 내재된 핵심 CSS

주요 스타일을 내재하는 것은 핵심 CSS를 불러오기 위해 필요한 왕복 요청을 제거한다. 나머지 CSS를 지연시키면 CSS 블로킹 시간을 최소화할 수 있다.

여러분이 사이트에 직접 내재된 스타일을 추가할 수 없는 경우 자동화된 처리를 해주는 라이브러리를 사용하라. 다음은 몇 가지 예이다.

  • Critical, CriticalCSS 그리고 Penthouse는 모두 상단부의 CSS를 추출하고 내재할 수 있는 패키지들이다.
  • Critters는 핵심 CSS를 내재하고 나머지 CSSS를 지연 로딩할 수 있도록 해주는 웹팩 플러그인이다.

Example of LCP improvement: Before and after inlining critical CSS LCP 개선의 예: 핵심 CSS를 내재하기 전과 후

더 자세한 내용은 핵심 CSS를 추출하기를 살펴보라.

자바스크립트 블로킹 시간을 줄인다.

사용자가 필요한 양만큼의 최소 자바스크립트를 다운로드할 수 있게 제공한다. 자바스크립트 블로킹을 줄이면 렌더링이 빨라지고 결과적으로 LCP도 좋아진다.

다음과 같은 몇 가지 방법으로 스크립트를 최적화하면 이 목표를 달성할 수 있다.

First Input Delay의 최적화 가이드는 자바스크립트 블로킹 시간을 줄일 수 있는 모든 기법을 좀 더 자세히 다룬다.

리소스 로드 시점을 늦춘다.

CSS와 자바스크립트의 블로킹 시간의 증가는 직접적으로 성능 저하를 초래하지만, 다른 많은 타입의 리소스를 로드하는데 걸리는 시간 또한 렌더링 시간에 영향을 미친다. LCP에 영향을 주는 엘리먼트의 타입은 다음과 같다.

  • <img> 엘리먼트
  • <svg> 엘리먼트 내부의 <image> 엘리먼트
  • <video> 엘리먼트 (지정된 경우, poster 이미지를 사용하여 LCP를 측정한다.)
  • CSS 그라데이션과 다르게 url() 함수를 통해서 로드된 배경 이미지가 있는 엘리먼트
  • 텍스트 노드나 인라인 레벨의 텍스트 엘리먼트를 포함하는 Block-level 엘리먼트

상단부가 렌더링된 경우 이런 엘리먼트를 로드하는데 걸리는 시간은 LCP에 직접적인 영향을 미친다. 이런 파일이 가능한 빠르게 로드될 수 있도록하는 몇 가지 방법이 있다.

  • 이미지를 최적화하고 압축한다.
  • 중요한 리소스를 미리 로드한다.
  • 텍스트 파일을 압축한다.
  • 네트워크 연결에 기반하여 다른 리소스를 제공한다(적응형 제공방식)
  • 서비스 워커를 사용하여 리소스를 캐시한다.

이미지를 최적화하고 압축한다.

많은 사이트에서 이미지는 페이지 로드가 완료되면 화면에서 가장 큰 요소다. 히어로 이미지, 큰 캐러셀(carousel)이나 배너 이미지는 모두 일반적인 예이다.

image 가장 큰 페이지의 엘리먼트로서 이미지: design.google

이런 종류의 이미지를 로드하고 렌더링하는데 걸리는 시간을 개선하는 것은 직접적으로 LCP를 빠르게 한다. 방법은 다음과 같다.

  • 애초에 이미지를 사용하지 않도록 한다. 내용과 관련 없는 경우 제거한다.
  • 이미지를 압축한다(예> Imagemin 사용)
  • JPEG 2000, JPEG XR, or Web와 같은 더 새로운 포맷으로 이미지를 변환한다.
  • 반응형 이미지를 사용한다.
  • 이미지 CDN을 사용한다.

이 모든 테크닉을 자세하게 설명한 자료는 이미지 최적화 가이드를 살펴보라.

중요한 리소스를 미리 로드한다.

때때로, 특정 CSS나 자바스크립트 파일에서 선언되거나 사용되는 중요한 리소스는 애플리케이션의 많은 CSS 파일 중 하나에 깊숙히 선언되어 있는 글꼴과 같이 원하는 시기보다 늦게 로드되기도 한다.

우선 순위를 높여야 하는 특정 리소스가 있다면, 보다 빨리 로드하기 위해서 <link rel="preload">를 사용한다. 다양한 종류의 리소스가 미리 로드될 수 있지만, 폰트, 상단부의 이미지나 비디오 그리고 핵심경로의 CSS나 자바스크립트처럼 핵심 리소스를 미리 로딩하는 것에 우선 집중해야 한다.

<link rel="preload" as="script" href="script.js" />
<link rel="preload" as="style" href="style.css" />
<link rel="preload" as="image" href="img.png" />
<link rel="preload" as="video" href="vid.webm" type="video/webm" />
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin />

크롬 73부터 미리 로드하기는 반응형 이미지와 함께 두 패턴을 조합하여 훨씬 빠른 이미지 로딩에 사용될 수 있다.

<link
  rel="preload"
  as="image"
  href="wolf.jpg"
  imagesrcset="wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w"
  imagesizes="50vw"
/>

텍스트 파일을 압축한다.

GzipBrotli같은 압축 알고리즘은 서버와 브라우저 사이에 전송되는 HTML, CSS, 자바스크립트와 같은 텍스트 파일의 크기를 상당히 줄일 수 있다. Gzip은 모든 브라우저에서 효과적으로 지원되고, 더 나은 압축 결과를 제공하는 Brotli는 대부분의 최신 브라우저에서 사용 가능하다.

리소스를 압축하면 전송 크기를 최소화하고 로드 시간을 개선하며 결과적으로 LCP가 개선된다.

  1. 먼저, 서버가 이미 자동으로 파일을 압축하고 있는지 확인한다. 대부분의 호스팅 플랫폼, CDN 그리고 리버스 프록시 서버는 기본적으로 리소스를 압축과 함께 인코딩도록 되어 있고 그렇지 않은 경우라도 쉽게 설정할 수 있다.
  2. 파일을 압축하기 위해 서버를 수정해야 하는 경우, 더 나은 압축율을 제공하기 때문에 gzip 대신 Brotli를 설정한다.
  3. 사용할 압축 알고리즘을 선택했다면, 브라우저에서 온 요청을 바로 압축 처리하지 말고 빌드 프로세스에 중에 미리 리소스를 압축한다. 이는 서버의 오버헤드를 최소화하고 요청이 있을 때, 특히 높은 압축률을 사용할 때 지연을 방지한다.

Example of LCP improvement: Before and after Brotli compression LCP 개선의 예: Brotli 압축 사용 전과 후

더 자세한 내용은 네트워크 페이로드를 최소화하고 압축하기 가이드를 참고하라.

적응형 제공방식

페이지의 주요 콘텐츠를 구성하는 리소스를 로드할 때, 사용자의 디바이스나 네트워크 환경에 따라 조건적으로 다른 리소스를 가져오는 것이 효과적일 수 있다. 이는 Network Information, Device MemoryHardwareConcurrency API를 사용하여 수행할 수 있다.

초기 렌더링에 핵심적이고 큰 리소스가 있을 때, 사용자의 연결이나 디바이스에 따라 같은 리소스의 다른 버전을 사용할 수 있다. 예를 들어, 4G 이하의 연결 속도에서는 비디오 대신 이미지를 표시할 수 있다.

if (navigator.connection && navigator.connection.effectiveType) {
  if (navigator.connection.effectiveType === '4g') {
    // Load video
  } else {
    // Load image
  }
}

다음은 유용하게 사용할 수 있는 프러퍼티 목록이다.

  • navigator.connection.effectiveType: 유효한 연결 타입
  • navigator.connection.saveData: 데이터 절약의 활성/비활성 여부
  • navigator.hardwareConcurrency: CPU 코어 갯수
  • navigator.deviceMemory: 디바이스 메모리

더 자세한 정보는 네트워크 품질에 따른 적응형 제공방식를 참고하라.

서비스 워커를 사용하여 리소스를 캐시한다.

서비스 워커는 서두에 언급한 더 작은 HTML 응답처리를 포함하여 많은 유용한 작업에 사용될 수 있다. 또한 반복적인 요청을 네트워크로부터 받는 대신 브라우저에게 제공할 수 있는 정적 리소스를 캐시하는데 사용될 수도 있다.

서비스 워커를 사용하여 핵심 리소스를 미리 캐시하는 것은, 특히 사용자가 약한 연결 상황이나 심지어는 오프라인일 때 접근한 페이지를 새로고침할 때 로드 시간을 크게 개선할 수 있다. Workbox와 같은 라이브러리는 이같은 처리를 위해 여러분이 직접 서비스 워커를 작성하는 것보다 더 쉽게 미리 캐시된 리소스를 업데이트할 수 있도록 프로세스를 만들어 준다.

서비스 워커와 Workbox에 대해 더 알고 싶다면 네트워크 안정성을 살펴보라.

클라이언트 측 렌더링

많은 사이트가 브라우저에서 페이지를 직접 렌더링하기 위해서 클라이언트 측 자바스크립트 로직을 사용한다. React, Angular 그리고 Vue와 같은 프레임워크와 라이브러리는 서버 대신 클라이언트에서 웹 페이지의 다양한 모습을 전적으로 처리하는 싱글 페이지 애플리케이션을 쉽게 만들 수 있다.

클라이언트에서 주로 렌더링되는 사이트를 만들고 있다면, 용량이 큰 자바스크립트 번들의 사용이 LCP에 미칠 수 있는 영향을 조심해야 한다. 최적화가 그것을 방지하지 못했다면, 모든 핵심 자바스크립트가 다운로드되고 실행완료 되기 전까지 사용자는 페이지의 어떠한 콘텐츠도 볼 수 없거나 동작하게 할 수 없을지도 모른다.

클라이언트 측에서 렌더링되는 사이트를 만들 때 다음과 같은 최적화를 고려해야 한다.

  • 핵심 자바스크립트를 최소화한다.
  • 서버 측 렌더링을 사용한다.
  • 사전 렌더링한다.

핵심 자바스크립트를 최소화한다.

얼마 간의 자바스크립트가 다운로드 된 후에 사이트의 콘텐츠가 단지 보이기만 하거나 상호작용만 된다면, 번들 크기를 가능한 많이 줄이는 것이 더 중요해진다. 이 작업은 다음을 통해 처리할 수 있다.

  • 자바스크립트를 최소화 하기
  • 사용되지 않는 자바스크립트를 지연시키기
  • 사용되지 않는 폴리필을 최소화하기

이런 최적화에 대해서 더 읽고 싶다면 자바스크립트 블로킹 타임을 줄이기로 돌아간다.

서버 측 렌더링을 사용한다.

자바스크립트의 용량을 최소화하는 것은 클라이언트에서 렌더링되는 대부분의 사이트에서 집중해야 할 첫번째가 되어야 한다. 그러나 LCP를 가능한 많이 개선하기 위해서는 서버 렌더링을 결합하는 것도 고려해야 한다.

이 개념은 애플리케이션을 HTML로 렌더링하기 위해 서버를 사용하는데, 클라이언트는 모든 자바스크립트와 필요한 데이터를 동일한 DOM 콘텐츠에 "hydrate"한다. 이 방법은 페이지의 주요 컨텐츠가 클라이언트에서만 렌더링되는 것이 아니라 서버에서 먼저 렌더링함으로써 LCP를 개선할 수 있다. 그러나 다음과 같은 몇 가지 단점이 있다.

  • 서버와 클라이언트에서 동일한 자바스크립트로 렌더링 애플리케이션을 유지해야 하므로 복잡도가 올라갈 수 있다.
  • 서버에서 HTML 파일을 렌더링하기 위해서 자바스크립트를 실행하는 것은 서버에서 그냥 정적 페이지를 제공하는 것과 비교해서 항상 서버 응답 시간(TTFB)이 늘어날 수 있다.
  • 서버 렌더링 페이지는 상호작용이 가능한 것처럼 보이지만, 모든 클라이언트 측 자바스크립트가 실행되기 전까지는 사용자에게 반응할 수 없다. 한마디로 Time to Interactive(TTI)를 악화시킬 수 있다..

사전 렌더링한다.

사전 렌더링하기는 서버 측 렌더링 보다 덜 복잡한 별도의 테크닉이며 애플리케이션의 LCP를 개선하기 위한 방법을 제공한다. 사용자 인터페이스가 없는 헤드리스 브라우저는 빌드하는 동안 모든 경로의 정적 HTML 파일을 생성하는데 사용된다. 이 파일들은 애플리케이션에 필요한 자바스크립트 번들과 함께 배포된다.

사전 렌더링하기는 TTI에 여전히 나쁜 영향을 미치지만, 페이지가 요청된 후에 동적으로 각 페이지를 렌더링하는 서버 측 렌더링 방법이므로 서버 응답 시간에는 영향을 주지 않는다.

Example of LCP improvement: Before and after pre-rendering LCP 개선의 예: 사전 렌더링하기 전과 후

다른 형태의 서버 렌더링 아키텍처를 보다 더 탐구해 보고 싶다면, 웹에서 렌더링하기를 읽어보라.

개발자 도구

다음은 LCP를 측정하고 디버깅하기 위해서 사용할 수 있는 도구들이다.

Lighthouse 6.0

  • 크롬 개발자 도구의 Performance 패널에 있는 Timings 섹션은 LCP 마커를 표시하고 Related Node 필드에 마우스를 올리면 어떤 엘리먼트가 LCP와 관련되어 있는지 보여준다.

LCP in Chrome DevTools

리뷰를 해준 Philip Walton, Katie Hempenius, Kayce Basques, and Ilya Grigorik에게 감사를 전한다.



FE Weekly Picks는 FE개발랩의 기술 활동으로,
프론트엔드관련 아티클을 직접 작성하거나 관심있는 영문 아티클을 선정하여 번역하고 공유합니다.


저작권 및 라이선스