일상이 개발

React 고급 패턴: 동적 import + Suspense로 똑똑하게 코드 분리하기

디어노미 2025. 4. 6. 06:33
반응형

🚀 동적 import와 Suspense의 고급 활용법

React의 코드 스플리팅을 위한 가장 대표적인 방법은 React.lazy()Suspense 조합이에요.
이전 글에서 살펴본 기본 구조는 다음과 같죠.

const MyComponent = React.lazy(() => import('./MyComponent'));

<Suspense fallback={<Loading />}>
  <MyComponent />
</Suspense>

이 방식은 아주 강력하고 간단하지만, 실제 프로젝트에서 더 유연하게 사용하기 위해서는
동적 import와 Suspense를 다양한 방식으로 활용할 줄 알아야 합니다.

이번 글에서는 React.lazy()의 한계를 넘어서,
비동기 상황 대응, 조건부 렌더링, 중첩 Suspense, 모듈 레벨 import, fallback 최적화
실전에서 유용한 고급 패턴들을 소개할게요.


✅ 1. 조건부 동적 import: 사용자가 클릭할 때만 로딩

모든 컴포넌트를 앱 시작과 동시에 lazy-load 할 필요는 없어요.
오히려 특정 사용자 행동이 발생할 때만 컴포넌트를 불러오는 것이 더 효과적일 수 있습니다.

예시: 버튼 클릭 시 모달을 불러오는 경우

import { useState, lazy, Suspense } from "react";

const LazyModal = lazy(() => import("./Modal"));

function Page() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(true)}>모달 열기</button>

      {showModal && (
        <Suspense fallback={<div>모달 로딩 중...</div>}>
          <LazyModal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </div>
  );
}
  • ✅ 초기에 모달 컴포넌트 코드가 로드되지 않음
  • ✅ 사용자가 클릭했을 때만 import 실행됨
  • ✅ 성능과 UX 모두 잡을 수 있음

✅ 2. 중첩 Suspense: 컴포넌트별 로딩 상태 관리

여러 개의 lazy 컴포넌트를 하나의 Suspense로 감싸면,
한 컴포넌트가 로드되지 않아도 전체 fallback이 뜨게 됩니다.

→ 컴포넌트별로 Suspense를 중첩해서 처리하면 좋아요.

<Suspense fallback={<div>헤더 로딩 중...</div>}>
  <Header />
</Suspense>

<Suspense fallback={<div>본문 로딩 중...</div>}>
  <Main />
</Suspense>
  • ✅ 전역에 하나만 사용하는 것보다 UX에 유리
  • ✅ 로딩 중에도 다른 영역 먼저 렌더링 가능

✅ 3. named export 모듈도 lazy-load 하기

React.lazy()는 기본적으로 default export만 지원해요.
하지만 일부 라이브러리나 구조는 named export만 사용하죠.

→ 이럴 땐 다음처럼 default wrapping을 해주면 됩니다.

const LazyChart = lazy(() =>
  import("./Chart").then((module) => ({ default: module.Chart }))
);
  • ✅ module.Chart는 named export
  • default: module.Chart 형태로 감싸야 React.lazy가 인식 가능

✅ 4. 비동기 로직 안에서 Suspense 적용

컴포넌트를 lazy-load 하는 시점을 상태나 조건에 따라 제어하고 싶을 수도 있죠.

function DynamicComponentLoader({ type }) {
  const Component = lazy(() =>
    import(`./components/${type}`).catch(() =>
      import("./components/NotFound")
    )
  );

  return (
    <Suspense fallback={<p>컴포넌트 로딩 중...</p>}>
      <Component />
    </Suspense>
  );
}
  • ✅ 동적 경로 지원
  • ✅ 예외 발생 시 fallback import 가능

✅ 5. fallback UI를 더 정교하게 구성하기

단순한 “로딩 중...” 대신 스켈레톤 UI / 애니메이션 / 에러 fallback 등을 활용하면 UX가 향상됩니다.

<Suspense fallback={isMobile ? <MobileSkeleton /> : <DesktopSkeleton />}>
  <Dashboard />
</Suspense>
  • ✅ 사용자 환경에 따라 fallback UI 다양화
  • ✅ 로딩 시 불쾌감 최소화

✅ 6. React.lazy 없이 직접 import()로 렌더링 제어하기

React.lazy() 대신 import()를 직접 사용해 컴포넌트를 수동으로 로딩할 수도 있어요.

function DynamicImportDemo() {
  const [Comp, setComp] = useState(null);

  useEffect(() => {
    import("./HeavyComponent").then((mod) => {
      setComp(() => mod.default);
    });
  }, []);

  if (!Comp) return <p>로딩 중...</p>;
  return <Comp />;
}
  • ✅ Suspense 없이 사용할 수 있음
  • ✅ 커스텀 로직/오류 핸들링/SSR 분리에 유리

✅ 7. React 18의 가능성: 서버 컴포넌트 + 스트리밍

React 18부터는 Suspense가 SSR에서도 작동 가능해졌습니다.
Next.js 13+에서는 서버 컴포넌트와 함께 스트리밍 렌더링을 지원해요.

<Suspense fallback={<p>댓글 로딩 중...</p>}>
  <CommentList />
</Suspense>
  • ✅ 서버에서 컴포넌트를 나눠서 보내고 클라이언트에서 조립
  • ✅ SSR + CSR 병합 UX 구성 가능
  • ⚠️ Next.js 기반에서만 가능

✅ 마무리 정리

React의 Suspense와 코드 스플리팅은 단순한 로딩 처리 도구를 넘어서
성능 + 사용자 경험 + 유지보수성까지 개선할 수 있는 강력한 전략입니다.

📌 오늘 배운 고급 활용법 요약

  • ✔ 조건부 import: 유저 액션 이후 로드
  • ✔ 중첩 Suspense: 컴포넌트별 로딩 처리
  • ✔ named export도 lazy-load 가능
  • ✔ 동적 import 경로/예외 대응
  • ✔ 다양한 fallback 구성으로 UX 강화
  • ✔ React.lazy 없이 import()로 직접 제어도 가능
  • ✔ React 18 이후 SSR + Suspense 연계 가능
Suspense는 단순히 ‘로딩 중...’을 보여주는 컴포넌트가 아닙니다.
React 앱 구조 자체를 최적화하는 도구예요! 😎

다음 글에서는 React 앱에서 로딩 상태 관리와 사용자 경험을 더욱 부드럽게 만드는 팁도 함께 다뤄볼게요!

반응형