[Web Vitals] Interaction to Next Paint (INP) 최적화

xoxristine·2024년 4월 1일
6

FE-ninjas

목록 보기
1/2

올해 3월 공식적으로 Core Web Vitals 항목 중 하나인 FID를 INP가 대체하게 되었다.
이제 Core Web Vitals 측정 항목은 LCP, CLS, INP가 되었는데 INP가 무엇이고 왜 변경되었는지, 적용방법까지 알아보자.

📗 FID(First Input Delay)

FID란 사용자가 페이지와 처음 상호 작용할 때(링크를 클릭하거나 버튼을 탭 하거나 사용자 지정 JavaScript 기반 컨트롤을 사용할 때)부터 해당 상호 작용에 대한 응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 측정하는 지표이다.

📘 INP (Interaction to Next Paint)

INP는 페이지에서 발생하는 모든 클릭과 탭 및 키보드 상호 작용을 측정하여 페이지의 전반적인 반응성을 나타내는 것이다.
사용자가 페이지 작업을 완료하면 관찰된 상호작용 중 가장 긴 상호작용이 페이지의 INP 값으로 선택된다.

상호작용(Interaction)이란?

input delay: 이벤트 핸들러가 실행되기 시작할 때까지 발생하며, 메인 스레드가 긴 작업을 수행하고 있을때 길어질 수 있음
processing time: 이벤트 핸들러가 실행되는데 걸리는 시간
presentation delay: 다음 프레임을 렌더링하고 페인팅하는 시간

상호작용이 걸린 시간: input delay + processing time + presentation delay

FID와 INP의 차이점

FIDINP
* 첫 번째 상호작용의 입력 지연만 측정* 모든 상호 작용 고려
* 이벤트 핸들러를 실행하는 데 걸리는 시간, 다음 프레임 표시 지연은 측정 X* 모든 상호 작용을 샘플링하여 반응성을 종합적으로 평가
input delayinput delay + processing time + presentation delay

따라서 반응성을 종합적으로 평가할 수 있으므로 INP가 FID보다 전반적인 반응성에 대한 더 신뢰할 수 있는 지표가 된다.

INP 측정 방법

여러 측정 도구가 있지만 아래 설명은 Lighthouse 기준으로만 작성했다.

개발자도구 Lighthouse에서 측정하는 방법은 아래 화면에서 기간 모드(timespan mode)를 선택하고 기간 모드 시작을 누른다.

그 다음 기간 모드 시작 중에서 기간 모드가 시작되었습니다. 상태로 바뀌면 버튼을 클릭하거나 텍스트 필드에 입력하는 등 원하는 대로 테스트 페이지와 상호 작용하고 기간 모드 종료를 누르면 보고서를 확인할 수 있다.

INP 개선 방법

상호작용이 걸린 시간: input delay + processing time + presentation delay 를 단축하는 방법은 optimize-inp 참고

📙 프로젝트 적용

개발 관련 북마크만 2000개 넘는 사람으로서..
인사이트 전체에서 인사이트를 검색할 때 받아오는 너무 데이터가 많아 느리다면
보여줄 수 있는 나머지 파트를 빠르게 상호작용하여 보여주는 것이 중요하다고 생각하여 검색 기능에 적용해보기로 했다.

사용자가 검색창에 javascript를 입력했을 때 이를 포함하는 컴포넌트가 전체 601개593개 출력되어야 하는 상황에서 먼저 INP를 측정했다.
검색 결과 출력하는 화면은 아래와 같다.

입력 전입력 후

실행해보면 인사이트 검색 창을 누르고 바로 입력했으나javascript를 타이핑하는데 끊기고 컴포넌트가 렌더링 되는 속도도 아주 느린 것을 볼 수 있다.

그 이유는 사용자가 키보드 입력을 할 때마다 매번 state가 계속 변경 되고, 그에 따라 api 호출과 data를 받와서 화면을 그리려는 준비까지 반복되기 때문에 그 동안에 interaction이 블로킹되기 때문이다.

위에서 언급한 방법인 Lighthouse의 timespan 모드를 이용해서 INP를 측정한 결과이다. INP 값이 500ms 이상인 경우에 속하여 페이지의 응답성이 낮다는 결과를 받았다.

실행 시간을 보면 표시 지연이 가장 오래 걸리는 것을 알 수 있다. 따라서 INP in Frameworks 문서를 바탕으로 useTransition을 사용해 입력 지연, 표시 지연 문제부터 개선해보기로 했다.

1. useTransition 적용

// 기존 코드
return (
  <SearchSection>
      <SearchIcon />
      <SearchInput
          placeholder="인사이트 검색"
          autoFocus
          onChange={(e) => setKeyword(e.target.value)}
      />
  </SearchSection>
)

먼저 렌더링 되었으면 좋겠는 부분: 사용자가 키보드를 입력해서 input이 출력되는 UI

startTransition을 이용해서 우선순위를 낮게 지정할 상태 업데이트를 transition이라고 표시해 리액트가 UI 렌더링시 우선순위에 따라 업데이트 할 수 있도록 한다. transition으로 표시된 setKeyword는 다른 상태 업데이트가 호출될 때 중단되어 사용자의 interaction을 블로킹하지 않고 자연스럽게 동작하게 된다.

//useTransition 적용 코드
const [isPending, startTransition] = useTransition();
const typingKeyword = (inputValue: string) => {
    startTransition(() => {
      setKeyword(inputValue);
    });
 };

return (
  <SearchSection>
      <SearchIcon />
      <SearchInput
          placeholder="인사이트 검색"
          autoFocus
          onChange={(e) => typingKeyword(e.target.value)}
      />
  </SearchSection>
)

startTransition 적용 전과 비교했을 때 사용자가 타이핑을 했을 때 막히지 않기 때문에 지연이 되지 않는 것처럼 느껴진다.

  • useTransition 적용 전 state가 변경되었을 때 useEffect로 확인한 모습

사용자가 입력 할 때마다 state 값이 바뀐다.

  • useTransition 적용 후 state가 변경되었을 때 useEffect로 확인한 모습

startTransition으로 setKeyword의 우선순위를 낮게 지정해 다른 업데이트가 있을 때 중단되고 모든 업데이트가 이루어졌을 때 state가 변경된 것을 확인할 수 있다.

총 소요 시간 660ms -> 570ms, 표시 지연 시간 110ms -> 30ms로 처리 시간을 제외한 시간이 줄어든 것을 확인할 수 있다.
하지만 이벤트 핸들러 처리 시간이 아직 오래 걸리기 때문에 isPending을 사용해보기로 했다.

2. isPending 사용

<ResultSection>
  {!isPending ? (
    searchData.map((value, i) =>
      $isSmall ? (
        <InsightCard key={i} insightData={value} />
      ) : (
        <SummaryInsightCard
          key={i}
          favicon="/svg/insight-favicon.svg"
          insightData={value}
        />
      )
    )
  ) : (
    <Image
      src={defaultImage}
      alt="default"
      width={400}
      height={400}
      className="thumbnail"
    />
  )}
</ResultSection>

isPending: startTransition 내의 함수 실행 완료 전: true, 실행 완료 후: false

isPending을 사용해서 setKeyword 실행 완료 전이라면 로고 이미지를, 실행 완료 되었다면 로고 이미지를 보여주도록 구현했다.

변경 전에는 처리 시간이 오래 걸리는 이유가 보고서에서 The 'keypress' event takes 500ms. 라고 했는데 isPending을 쓰는데 속도가 개선된 이유가

pending?isPending XisPending O
Odata.map(...) 컴포넌트를 그리기 직전까지의 시간keypress event 처리되는 시간이 input을 받고 ~ image를 그리기 전 직전까지의 시간
xdata.map(...) 컴포넌트를 그리기 직전까지의 시간data.map(...) 컴포넌트를 그리기 직전까지의 시간

으로 Pending Transtion이 있을 때 많은 데이터를 그려내려는 노력을 하지 않아서 INP 지표를 개선할 수 있었다.

끝!

+) Debounce vs startTransition

startTransition이 Debouncing과 유사한 일을 한다고 생각하여 비교하여 차이점을 적어보면 다음과 같다.

DebouncestartTransition
* 고정된 시간만큼 기다려야함* 사용자가 사용하는 기기, 환경에 따라 유동적으로 지연됨
* 만약 좋은 PC 환경에서 실행하여 지연이 거의 없는 경우에도 무조건 설정한 시간만큼 기다려야함* 좋은 PC 환경에서 실행하여 지연이 없는 경우에 transition으로 지정된 우선순위 낮은 업데이트가 비교적 빨리 실행될 수 있음
profile
🔥🦊

0개의 댓글