컴포넌트 Lazy Loading, prefetch로 최적화하기

제리추·2024년 8월 13일
43
post-thumbnail

🐹 들어가기 앞서

해당 글은 React 컴포넌트 prefetch 관련한 최적화 글입니다. Next를 사용하고 계신다면 Link 컴포넌트의 prefetch를 통해 Next가 알아서 다음 라우터에 대한 정보를 미리 로드하기 때문에 읽으실 필요가 없습니다 :)

회사에 입사하고 벌써 반년이 넘었습니다. 입사한 직후 플랫폼을 보고 한 일은 3년 된 회사 메인 솔루션을 최적화하는 작업을 진행했습니다. React로 되어 있었지만 컴포넌트 최적화가 되어 있지 않아 FCP가 느려 DX(개발자 경험)와 UX(사용자 경험)이 좋지 않았습니다.
문제점을 해결하기 위해 Suspense, lazy를 사용해 code spliting 하는 작업을 진행하였고 그 결과 FCP가 50% 이상 줄어드는 결과를 나타냈습니다. 하지만 code spliting을 사용하면 단점들이 존재하게 되는데 당시 다른 프로젝트를 맡게 되어 하지 못한 최적화를 7개월이 지난 지금 다시 적용하며 글을 씁니다 :)

🐹 기존 코드 소개

default-code-image

우선 7개월 전 code spliting을 통해 다른 페이지로 들어갈 때, 해당 페이지를 그 시점에 로드하도록 설정했습니다. code spliting을 할 경우 최초 렌더링 속도는 향상되겠지만, 다른 페이지로 이동할 때, 렌더링 하는 시간이 추가적으로 걸린다는 단점이 있습니다.

그리고 DeferredComponent를 통해 0.25초 내에 렌더링 되는 것들은 Suspense 내에 Loading 컴포넌트가 동작을 하지 않도록 설정을 해뒀습니다! 그 이유로는 빠르게 렌더링일 될 경우 Loading 컴포넌트가 깜빡거리게 된다면 오히려 Ux에 방해가 되기 때문입니다.

하지만 0.25초가 넘어가게 된다면 로딩 스피너가 작동하게 되는데 이 스피너가 작동되지 않고 즉시 사용자가 페이지를 볼 수 있도록 설정하려고 합니다.

- 해당 관련 글을 보고 싶으시면 여기를 참고해주세요 :)

다른 페이지로 이동 렌더링 프로세스입니다.
1. 페이지 로드 (네트워크)
2. 로드한 페이지의 Js Evaluate (메인 쓰레드)
3. Js 실행 (메인 쓰레드)

그렇다면 1번과 2번의 실행 시간을 줄이기 위해선 어떻게 해야 할까요?

✨ 컴포넌트를 일찍 출근 시키면 됩니다. (prefetch하면 됩니다) ✨

🐹 기존 코드 문제점

lazy-loading-issue

lazy-code-image

보이시나요? 현재 처음 들어간 페이지는 캐싱 처리가 되지 않아 컴포넌트 파일을 렌더링 하는 것에 Spinner가 작동하게 됩니다. Spinner가 발생하는 이유는 현재 페이지들을 Lazy하게 Load하기 때문에 페이지 진입 시 렌더링이 진행되기 때문입니다.

조사에 따르면 렌더링 속도는 빠를수록 좋습니다. 웹 성능 사업부에 근무하는 고메즈(Gomez)에 따르면, 사용자의 40%가 3초 동안 기다리게 되면 사이트를 이탈하는 것으로 나타났습니다. 월마트(Walmart)는 페이지 속도를 100ms까지 개선하면 수익이 1% 증가한다는 사실을 발견했습니다.

🐹 해결 방안

위 문제점을 해결하는 방법으로는 2가지 방법이 있습니다.

1. 다음 페이지로 넘어갈 버튼을 hover 시에 페이지를 prefetch합니다.

onMouseEnter 이벤트를 통해 prefetching을 실행합니다. 다른 페이지로 넘어가기 위해, 버튼에 호버한 후에 클릭하기까지 걸리는 시간이 보통 0.2~0.5초아고 합니다. 이 시간은 페이지를 미리 로드하기 위한 충분한 시간입니다.

preload-hook-image

먼저 컴포넌트를 prefetch할 hook을 만들어줍니다!

preload-hook-use-image
  return (
    <div>
      <p>{calAverage(arr)}</p>
      <button onMouseOver={() => handleMouseOverPrefetch(path, lazyLoad)}>예시 버튼</button>
    </div>
  );

버튼에 MouseOver하게 된다면 위와 같이 해당 함수를 사용하게 됩니다! 적용하고 보니 프로젝트 build 시에 문제가 생겼습니다.

버그 픽스

해당 문제는 vite를 사용할 시에 import가 제대로 빌드 되지 않아 문제가 생깁니다. 해당 문제를 해결하기 위해 3가지 방법이 존재했습니다.

  1. meta.glob를 사용하는 방법
  2. 명시적으로 경로를 prefetch 해주는 방법
  3. 패키지를 설치해 사용하는 방법이 있었습니다.

명시적으로 작성하는 방법은 프로젝트 내에서 페이지가 많기 때문에 제외하였고, 패키지를 설치하는 방법은 빌드 사이즈 문제로 제외하고 meta.glob를 사용하여 해결했습니다.

하지만 문제점이 있습니다. 즉시 렌더링 되어야 하는 모달이나 페이지의 파일 크기가 큰 경우 0.2~0.5초 시간보다 더 오래 load할 수 있습니다. 이런 상황에는 다른 방법을 사용해야합니다.

2. 현재 페이지 컴포넌트 마운트 이후 prefetch 하는 방법

preload-issue2

preload-hook-use-image

로그인 이후 진입하는 Router와 대시보드 페이지가 파일이 커서 hover prefetch를 통해 빠르게 로드하지 못합니다. 따라서 페이지의 최초로 로드 이후 useEffect 빈 dependency를 사용해서 파일을 prefetch합니다.

  1. 페이지의 최초 마운트를 실행합니다. 모든 마운트가 끝난 후
  2. prefetch를 진행합니다.

🐹 결론

  • code spliting과 컴포넌트 prefetch를 진행하며 최적화를 진행하며 아래와 같은 많은 이점을 얻어 갈 수 있었습니다. 사용자 경험 개선을 위해 한번 적용해 보는 거 어떨까요?
  • 피드백과 조언은 언제나 환영입니다 !!
  1. 초기 로딩 시간 (FCP) 단축
  2. 빠른 로딩 시간을 통해 사용자 경험을 (UX) 개선했습니다.
  3. 사용자 행동 패턴을 예측해 다음에 필요할 가능성이 높은 컴포넌트를 미리 로드하며 로딩 지연을 최소화했습니다.
  4. 주요 컴포넌트를 미리 로드하며 네트워크 병목 형상을 줄일 수 있습니다.
  5. 컴포넌트를 비동기적으로 미리 로드하며 다른 리소스와 병렬로 로드가 가능해 로딩 시간에 긍정적인 역할을 합니다.
profile
안녕하세요. 소프트웨어 엔지니어 제리입니다 🐹

6개의 댓글

comment-user-thumbnail
2024년 8월 16일

코드 스플리팅을 할 때 항상 모듈을 불러오는 데 비는 시간이 있어 단점이라고 생각했는데,
리액트에서도 prefetch를 활용해서 이를 개선할 수 있다는 점을 처음 알았습니다!
글 잘 보고 갑니다 감사합니다

1개의 답글
comment-user-thumbnail
2024년 8월 17일

좋은 글 감사합니다 :)) prefetch들이 이런식으로 작동하는거였군요

1개의 답글
comment-user-thumbnail
2024년 8월 24일

미친 해결사 입니다

답글 달기
comment-user-thumbnail
2024년 8월 27일

d5as564da65sd46as

답글 달기