Suspense
React Suspense
비동기 렌더링을 선언적으로 처리. 로딩 상태 관리 간소화.
React Suspense
비동기 렌더링을 선언적으로 처리. 로딩 상태 관리 간소화.
React Suspense는 컴포넌트의 렌더링을 데이터가 준비될 때까지 "일시 중단"하고 대체 UI(fallback)를 표시하는 선언적 로딩 처리 메커니즘입니다. if (loading) 조건문 없이도 깔끔하게 비동기 상태를 관리할 수 있습니다.
Suspense는 원래 React.lazy와 함께 코드 스플리팅용으로 도입되었지만, React 18부터 데이터 페칭에도 사용할 수 있게 확장되었습니다. 서버 컴포넌트의 async 함수나 TanStack Query의 suspense 옵션과 결합하여 동작합니다.
여러 Suspense boundary를 중첩하여 사용하면 세분화된 로딩 상태를 관리할 수 있습니다. 부모 Suspense는 자식들이 모두 준비될 때까지 기다리고, 각 자식 Suspense는 독립적으로 로딩 상태를 처리합니다.
Streaming SSR에서 Suspense는 핵심 역할을 합니다. 서버에서 Suspense boundary까지 렌더링한 후 fallback을 포함한 HTML을 먼저 전송하고, 데이터가 준비되면 나머지를 스트리밍으로 보내 점진적으로 채워넣습니다.
import { Suspense, lazy } from 'react';
// 1. 코드 스플리팅과 Suspense
const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function Dashboard() {
return (
<div>
<h1>대시보드</h1>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart />
</Suspense>
<Suspense fallback={<div>관리자 패널 로딩중...</div>}>
<AdminPanel />
</Suspense>
</div>
);
}
// 2. TanStack Query와 Suspense
import { useSuspenseQuery } from '@tanstack/react-query';
function UserProfile({ userId }: { userId: string }) {
// suspense: true 옵션이 자동 활성화
const { data: user } = useSuspenseQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// data는 항상 존재 (undefined 체크 불필요)
return <div>{user.name}</div>;
}
function ProfilePage({ userId }: { userId: string }) {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile userId={userId} />
</Suspense>
);
}
// 3. 중첩 Suspense로 세분화된 로딩
function ProductPage({ id }: { id: string }) {
return (
<Suspense fallback={<PageSkeleton />}>
<ProductInfo id={id} />
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews productId={id} />
</Suspense>
<Suspense fallback={<RelatedSkeleton />}>
<RelatedProducts productId={id} />
</Suspense>
</div>
</Suspense>
);
}
// 4. ErrorBoundary와 함께 사용
import { ErrorBoundary } from 'react-error-boundary';
function SafeDataFetch() {
return (
<ErrorBoundary fallback={<ErrorMessage />}>
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
</ErrorBoundary>
);
}
// 5. SuspenseList로 로딩 순서 제어 (실험적)
import { SuspenseList } from 'react';
function Feed() {
return (
<SuspenseList revealOrder="forwards" tail="collapsed">
<Suspense fallback={<PostSkeleton />}>
<Post id="1" />
</Suspense>
<Suspense fallback={<PostSkeleton />}>
<Post id="2" />
</Suspense>
</SuspenseList>
);
}
기술 미팅에서:
"로딩 스피너가 너무 많아서 정신없어요. Suspense boundary를 적절히 묶으면 하나의 스켈레톤으로 깔끔하게 처리할 수 있어요. UX팀이랑 로딩 단위 협의해보죠."
기술 면접에서:
"Suspense의 동작 원리는요?" - "자식 컴포넌트가 Promise를 throw하면 Suspense가 catch해서 fallback을 렌더링합니다. Promise가 resolve되면 다시 자식을 렌더링합니다."
코드 리뷰에서:
"useSuspenseQuery 쓰면 if (isLoading) 체크 안 해도 돼요. 대신 반드시 Suspense로 감싸야 하고, ErrorBoundary도 같이 넣어서 에러 처리하세요."