최초 입력 반응 시간 최적화

사용자 상호작용에 더 빠르게 응답하는 방법

아디 오스마니
애디 오스마니

클릭했지만 아무 반응이 없습니다. 이 페이지와 상호작용할 수 없는 이유는 무엇인가요? 😢

콘텐츠가 포함된 첫 페인트 (FCP)와 최대 콘텐츠 렌더링 시간 (LCP)은 모두 페이지에서 콘텐츠가 시각적으로 렌더링 (페인트)되는 데 걸리는 시간을 측정하는 측정항목입니다. 페인트 시간은 로드 반응성, 즉 페이지가 사용자 상호작용에 얼마나 빠르게 반응하는지를 포착하지 않습니다.

첫 입력 지연 (FID)은 사이트의 상호작용과 반응에 대한 사용자의 첫인상을 캡처하는 코어 웹 바이탈 측정항목입니다. 사용자가 페이지와 처음 상호작용한 시점부터 브라우저가 실제로 상호작용에 응답할 수 있는 시점까지의 시간을 측정합니다. FID는 필드 측정항목이며 실험실 환경에서 시뮬레이션할 수 없습니다. 응답 지연을 측정하려면 실제 사용자 상호작용이 필요합니다.

양호한 충실도 값은 2.5초이고, 좋지 않은 값은 4.0초보다 크며, 그 사이의 값은 개선이 필요합니다.

실습에서 FID를 예측할 수 있도록 총 차단 시간 (TBT)을 권장합니다. 각각 다른 항목을 측정하지만 TBT의 개선은 일반적으로 FID의 향상에 해당합니다.

잘못된 FID의 주요 원인은 과중한 JavaScript 실행입니다. JavaScript가 웹페이지에서 파싱, 컴파일, 실행되는 방식을 최적화하면 FID가 직접적으로 줄어듭니다.

과도한 자바스크립트 실행

브라우저는 기본 스레드에서 JavaScript를 실행하는 동안 대부분의 사용자 입력에 응답할 수 없습니다. 즉, 기본 스레드가 사용 중일 때는 브라우저가 사용자 상호작용에 응답할 수 없습니다. 이를 개선하는 방법은 다음과 같습니다.

긴 작업 분리

이미 단일 페이지에 로드되는 자바스크립트의 양을 줄이려고 했다면 장기 실행 코드를 작은 비동기 작업으로 나누는 것이 유용할 수 있습니다.

긴 작업은 사용자가 UI가 응답하지 않는 것을 찾을 수 있는 JavaScript 실행 기간입니다. 기본 스레드를 50ms 이상 차단하는 코드는 긴 태스크로 특성화할 수 있습니다. 긴 작업은 자바스크립트 팽창의 징후입니다 (현재 사용자에게 필요한 것보다 더 많은 것을 로드하고 실행). 장기 작업을 분할하면 사이트의 입력 지연을 줄일 수 있습니다.

Chrome DevTools의 장기 작업
성능 패널에 장기 작업을 시각화하는 Chrome DevTools

코드 분할 및 장기 작업 분할과 같은 권장사항을 채택하면 FID가 눈에 띄게 향상됩니다. TBT는 필드 측정항목은 아니지만, 궁극적으로 상호작용까지의 시간 (TTI)과 FID를 모두 개선하기 위한 진행 상황을 확인하는 데 유용합니다.

상호작용 준비에 맞게 페이지 최적화

JavaScript에 크게 의존하는 웹 앱에서 FID 및 TBT 점수가 낮아지는 일반적인 원인은 다음과 같습니다.

퍼스트 파티 스크립트를 실행하면 상호작용 준비 상태가 지연될 수 있습니다.

  • 자바스크립트 크기 팽창, 과도한 실행 시간, 비효율적인 분할로 인해 페이지가 사용자 입력에 반응하는 속도가 느려지고 FID, TBT, TTI에 영향을 미칠 수 있습니다. 코드와 기능을 점진적으로 로드하면 이러한 작업을 분산하고 상호작용 준비 상태를 개선하는 데 도움이 될 수 있습니다.
  • 서버 측 렌더링 앱은 화면에 픽셀이 빠르게 그려지는 것처럼 보일 수 있지만, 대규모 스크립트 실행으로 인해 사용자 상호작용이 차단되는 경우 (예: 이벤트 리스너를 연결하기 위한 하이드레이션)에 유의하세요. 경로 기반 코드 분할을 사용하는 경우 수백 밀리초, 때로는 몇 초가 걸릴 수 있습니다. 빌드 시간에 더 많은 로직을 서버 측으로 이동하거나 더 많은 콘텐츠를 정적으로 생성하는 것이 좋습니다.

다음은 애플리케이션의 퍼스트 파티 스크립트 로드를 최적화하기 전과 후의 TBT 점수입니다. 필수적이지 않은 구성요소에 비용이 많이 드는 스크립트 로드 (및 실행)를 중요한 경로에서 벗어남으로써 사용자는 페이지와 훨씬 더 빠르게 상호작용할 수 있습니다.

퍼스트 파티 스크립트를 최적화한 후 Lighthouse에서 TBT 점수가 개선되었습니다.

데이터 가져오기는 상호작용 준비의 여러 측면에 영향을 미칠 수 있습니다

  • 연쇄적 가져오기의 폭포식 구조 (예: 자바스크립트 및 구성요소의 데이터 가져오기)를 기다리면 상호작용 지연 시간이 영향을 받을 수 있습니다. 단계식 데이터 가져오기에 대한 의존을 최소화하는 것을 목표로 합니다.
  • 대규모 인라인 데이터 스토어는 HTML 파싱 시간을 푸시하고 페인트 및 상호작용 측정항목에 모두 영향을 미칠 수 있습니다. 클라이언트 측에서 후처리해야 하는 데이터의 양을 최소화하는 것을 목표로 합니다.

서드 파티 스크립트 실행으로 인해 상호작용 지연 시간도 지연될 수 있습니다.

  • 많은 사이트에는 네트워크를 사용 중 상태로 유지하고 기본 스레드가 주기적으로 응답하지 않게 하여 상호작용 지연 시간에 영향을 주는 서드 파티 태그 및 분석 기능이 포함되어 있습니다. 서드 파티 코드의 주문형 로드를 살펴보세요 (예: 스크롤해야 볼 수 있는 광고는 표시 영역에 가깝게 스크롤될 때까지 로드하지 않을 수 있음).
  • 경우에 따라 서드 파티 스크립트가 기본 스레드의 우선순위 및 대역폭 측면에서 퍼스트 파티 스크립트를 선점하여 페이지가 상호작용 준비 상태를 얼마나 지연시킬 수도 있습니다. 사용자에게 가장 큰 가치를 제공한다고 생각하는 것을 먼저 로드하세요.

웹 작업자 사용

차단된 기본 스레드는 입력 지연의 주요 원인 중 하나입니다. 웹 작업자를 사용하면 백그라운드 스레드에서 JavaScript를 실행할 수 있습니다. 비 UI 작업을 별도의 작업자 스레드로 이동하면 기본 스레드 차단 시간이 단축되어 FID가 개선될 수 있습니다.

다음 라이브러리를 사용하면 사이트에서 웹 작업자를 더 쉽게 사용할 수 있습니다.

  • Comlink: postMessage를 추상화하고 더 쉽게 사용할 수 있도록 하는 도우미 라이브러리
  • Workway: 범용 웹 작업자 내보내기
  • Workerize: 모듈을 웹 작업자로 이동합니다.

자바스크립트 실행 시간 단축

페이지의 자바스크립트 양을 제한하면 브라우저가 자바스크립트 코드를 실행하는 데 필요한 시간이 줄어듭니다. 이렇게 하면 브라우저가 사용자 상호작용에 얼마나 빨리 반응할 수 있는지가 빨라집니다.

페이지에서 실행되는 자바스크립트의 양을 줄이는 방법은 다음과 같습니다.

  • 사용하지 않는 JavaScript 연기
  • 사용하지 않는 폴리필 최소화

사용하지 않는 JavaScript 연기

기본적으로 모든 JavaScript는 렌더링을 차단합니다. 브라우저에서 외부 JavaScript 파일로 연결되는 스크립트 태그를 발견하면 실행 중인 작업을 일시중지하고 JavaScript를 다운로드, 파싱, 컴파일, 실행해야 합니다. 따라서 페이지에 필요하거나 사용자 입력에 응답하는 데 필요한 코드만 로드해야 합니다.

Chrome DevTools의 범위 탭에서 웹페이지에서 사용되지 않는 JavaScript의 양을 확인할 수 있습니다.

범위 탭

사용하지 않는 JavaScript를 줄이는 방법은 다음과 같습니다.

  • 번들을 여러 개의 청크로 코드 분할
  • async 또는 defer를 사용하여 서드 파티 스크립트를 포함하여 중요하지 않은 JavaScript를 연기합니다.

코드 분할은 큰 자바스크립트 번들 하나를 조건부로 로드할 수 있는 작은 청크로 분할하는 (지연 로드라고도 함) 개념입니다. 대부분의 최신 브라우저는 동적 가져오기 구문을 지원하므로 주문형으로 모듈을 가져올 수 있습니다.

import('module.js').then((module) => {
  // Do something with the module.
});

특정 사용자 상호작용 (예: 경로 변경 또는 모달 표시)에서 JavaScript를 동적으로 가져오면 초기 페이지 로드에 사용되지 않는 코드를 필요할 때만 가져올 수 있습니다.

동적 가져오기 구문은 일반적인 브라우저 지원 외에도 다양한 빌드 시스템에서 사용할 수 있습니다.

  • webpack, Rollup, Parcel을 모듈 번들러로 사용하는 경우 동적 가져오기 지원을 활용하세요.
  • React, Angular, Vue와 같은 클라이언트 측 프레임워크는 구성요소 수준에서 더 쉽게 지연 로드할 수 있도록 추상화를 제공합니다.

코드 분할 외에도 중요한 경로 또는 스크롤 없이 볼 수 있는 콘텐츠에 필요하지 않은 스크립트에는 항상 async 또는 defer를 사용하세요.

<script defer src="…"></script>
<script async src="…"></script>

특별한 이유가 없는 한, 모든 서드 파티 스크립트는 기본적으로 defer 또는 async를 사용하여 로드되어야 합니다.

사용하지 않는 폴리필 최소화

최신 JavaScript 구문을 사용하여 코드를 작성하고 최신 브라우저 API를 참조하는 경우 이전 브라우저에서 작동하도록 하려면 코드를 트랜스파일하고 polyfill을 포함해야 합니다.

사이트에 polyfill 및 트랜스파일된 코드를 포함할 경우의 주요 성능 문제 중 하나는 최신 브라우저에서 필요하지 않은 경우 다운로드할 필요가 없다는 것입니다. 애플리케이션의 JavaScript 크기를 줄이려면 사용하지 않는 polyfill을 최대한 최소화하고 필요한 환경으로 사용을 제한하세요.

사이트에서 폴리필 사용을 최적화하려면 다음 단계를 따르세요.

  • Babel을 트랜스파일러로 사용하는 경우 @babel/preset-env를 사용하여 타겟팅하려는 브라우저에 필요한 polyfill만 포함합니다. Babel 7.9의 경우 bugfixes 옵션을 사용 설정하여 불필요한 polyfill을 더 줄이세요.
  • module/nomodule 패턴을 사용하여 두 개의 별도 번들을 전송합니다 (@babel/preset-envtarget.esmodules를 통해서도 이를 지원함).

    <script type="module" src="modern.js"></script>
    <script nomodule src="legacy.js" defer></script>
    

    Babel로 컴파일된 여러 최신 ECMAScript 기능이 JavaScript 모듈을 지원하는 환경에서 이미 지원되고 있습니다. 이렇게 하면 실제로 필요한 브라우저에 트랜스파일된 코드만 사용되도록 하는 프로세스를 간소화할 수 있습니다.

개발자 도구

FID를 측정하고 디버그하는 데 사용할 수 있는 도구는 다음과 같습니다.

  • Lighthouse 6.0은 필드 측정항목이므로 FID를 지원하지 않습니다. 하지만 총 차단 시간 (TBT)을 프록시로 사용할 수 있습니다. TBT를 개선하는 최적화는 필드의 FID도 개선되어야 합니다.

    Lighthouse 6.0

  • Chrome 사용자 환경 보고서는 출처 수준에서 집계된 실제 FID 값을 제공합니다.

리뷰를 작성해 주신 필립 월튼, 케이스 바스크, 일리야 그리고릭, 애니 설리반에게 감사드립니다.