본문 바로가기
Suspense실무 경험

React Lazy + Suspense 사용 시 페이지 전환 깜빡임(흰 화면) 문제 해결

4분실무 경험티스토리에서 옮긴 글

이전에 WiniLogis 프로젝트의 성능 최적화를 진행하면서 코드 스플리팅을 적용한 경험이 있습니다.

기존에는 SPA 특성상 메인 페이지에 접속하면 모든 페이지의 리소스가 하나의 JS 파일로 번들링되어 로드되고 있었습니다.

이로 인해 초기 로딩 시 불필요한 코드까지 함께 로드되는 문제가 있었습니다.

이를 개선하기 위해 React의 Lazy와 Suspense를 활용하여 도메인 단위로 파일을 분리하고, 필요한 시점에만 리소스를 불러오도록 구조를 변경했습니다.

그 결과 메인 페이지에서 로드되는 코드 양을 281,862줄 → 63,303줄로 줄이며 성능을 개선할 수 있었습니다.

또한 이외에도 이미지 리소스 최적화, i18n 기반 다국어 처리 개선 등을 함께 진행하며 전반적인 성능 점수와 접근성 점수도 개선할 수 있었습니다.

문제 상황

그림 1. Before

코드 스플리팅 적용 이후, 한 가지 문제가 발생했습니다.

페이지 이동 시마다 화면이 순간적으로 사라지며 흰 화면이 노출되는 현상이 있었습니다.

또한 SPA임에도 불구하고 페이지 전환이 자연스럽지 않고 뚝뚝 끊기는 느낌을 받게 되었습니다.

원인

App 최상단에서 Suspense를 다음과 같이 사용하고 있었습니다.

App.tsx

jsx
/* Lazy Loading 적용 */
const Main = lazy(() => import('./components/main'));

export default function App() {
  return (
    <Suspense fallback={<></>}>
      <Routes>
        <Route path='/' element={<Main />} />
        ...
      </Routes>
    </Suspense>
  );
}

fallback을 비워둔 이유는 다음과 같았습니다.

  • 페이지별 API 로딩은 각 페이지에서 개별적으로 처리
  • 페이지 전환 시 글로벌 로딩 UI는 UX적으로 적절하지 않다고 판단

하지만 이 구조로 인해 lazy 컴포넌트가 로딩되는 순간 Suspense가 fallback을 렌더링하게 되고, 결과적으로 화면이 비워지면서 흰 화면이 노출되는 문제가 발생했습니다.

고민한 점

이 문제를 해결하기 위해 몇 가지 방향을 고민했습니다.

하지만 이 방식은 각 컴포넌트마다 Suspense와 fallback 처리를 별도로 해줘야 하고, 그에 따라 관리 포인트가 늘어나면서 코드 복잡도가 증가할 수 있다고 판단했습니다.

또한 React의 Suspense 자체를 걷어내고, 코드 스플리팅 없이 일반적인 SPA 구조로 사용하는 방안도 함께 고민했습니다.

하지만 현재 프로젝트의 규모가 큰 상황에서 청킹을 제거할 경우 100KB 이상 크기의 번들 파일이 다수 발생하게 되고, 이는 초기 로딩 성능에 부정적인 영향을 다시 줄 수 있다고 판단했습니다.

결과적으로 코드 스플리팅 구조는 유지하면서 UX 문제를 해결할 수 있는 방향 을 찾는 것이 필요했습니다.

해결 과정

이후 해결 방법을 찾던 중, react-router-dom의 v7_startTransition 옵션을 통해 문제를 해결할 수 있었습니다.

index.tsx

jsx
...

root.render(
  <>
    <BrowserRouter future={{ v7_startTransition: true }}>
      <App />
    </BrowserRouter>
  </>
);

해당 옵션을 활성화하면 라우트 전환이 내부적으로 React의 startTransition으로 감싸지게 됩니다.

startTransition은 상태 업데이트를 transition으로 처리하여, 작업이 진행되는 동안 기존 UI를 유지할 수 있도록 도와주는 기능입니다.

기존에는 페이지 전환 시 lazy 컴포넌트가 로딩되면서 Suspense에 의해 fallback UI가 즉시 렌더링되었고, 이로 인해 흰 화면이 노출되었습니다.

하지만 이 옵션을 적용한 이후에는 페이지가 suspend되더라도 transition 상태로 처리되기 때문에 기존 화면이 유지된 채 로딩이 진행됩니다.

그 결과 lazy 컴포넌트 로딩 중에도 fallback(<></>)이 즉시 노출되지 않고 기존 화면이 유지되며, 로딩이 완료된 시점에 자연스럽게 다음 화면으로 전환되어 사용자 입장에서는 끊김 없이 페이지가 전환되는 것처럼 느껴지게 되었고, 페이지 이동 시 발생하던 흰 화면 문제를 해결할 수 있었습니다.

그림 2. After (페이지 전환 과정에서 흰 화면이 발생하는 것처럼 보일 수 있으나, 실제 동작에는 흰 화면이 발생하지 않습니다.), 화면이 번쩍이는건 mov to gif 파일 변환 중 발생한것으로 보입니다.

느낀 점

처음에는 코드 스플리팅 구조나 Suspense 사용 방식의 문제라고 생각해, 청킹 단위를 더 세분화하거나 구조를 변경하는 방향을 고민했습니다.

하지만 큰 구조를 변경하지 않고도 옵션 한 줄로 문제를 해결할 수 있었고, 이번 경험을 통해 또 하나를 배울 수 있었습니다.

또한 개발이 끝난 이후, react-router-dom을 v7으로 업데이트하면 v7_startTransition 옵션이 기본적으로 적용되어 별도의 설정 없이도 동일한 효과를 얻을 수 있다는 점을 알게 되었습니다. 이에 따라 react-router-dom을 7버전 이상으로 업데이트했습니다.

  • #React Lazy
  • #react-router-dom
  • #react-router-dom v7
  • #starttransition
  • #Suspense
  • #ux 개선
  • #v7_startTransition
  • #WiniLogis
  • #랜더링 우선순위
  • #성능 최적화

실무 경험의 다른 글