MAKITTDocs

useSuspenseQueries에는 반드시 Suspense Boundary 필요

docs/reference/shop-app-suspense-boundary.md


title: Shop App — useSuspenseQueries와 Suspense Boundary created: 2026-03-28

useSuspenseQueries에는 반드시 Suspense Boundary 필요

문제

Shop 앱에서 auth/me API가 ERR_INSUFFICIENT_RESOURCES로 무한 호출되며, 모든 클라이언트 interaction(hover, navigate, modal 등)이 동작하지 않는 치명적 버그 발생.

원인

PageRenderer에서 useResourcesData()useSuspenseQueries()를 사용하는데, 이를 감싸는 <Suspense> boundary가 없었음.

// ❌ BAD — Suspense boundary 없이 useSuspenseQueries 사용 <HydrationBoundary state={dehydrate(queryClient)}> <PageRenderer ... /> {/* 내부에서 useSuspenseQueries 호출 */} </HydrationBoundary>

왜 무한 루프가 되는가

  1. useSuspenseQueries는 데이터가 없으면 Promise를 throw함 (React Suspense 프로토콜)
  2. React는 가장 가까운 <Suspense> boundary를 찾아서 fallback을 렌더링해야 함
  3. <Suspense>가 없으면 React는 전체 컴포넌트 트리를 다시 렌더하려 시도
  4. 다시 렌더 → 다시 throw → 다시 렌더 → 무한 루프
  5. 수백~수천 번 반복 → 브라우저 리소스 고갈 → ERR_INSUFFICIENT_RESOURCES

왜 SSR은 정상이었는가

SSR에서는 prefetchShopResources()가 QueryClient에 데이터를 미리 넣어둠. HTML은 정상 렌더링됨. 문제는 클라이언트 hydration 시점에서만 발생.

해결

// ✅ GOOD — Suspense boundary로 감싸기 <HydrationBoundary state={dehydrate(queryClient)}> <Suspense> <PageRenderer ... /> </Suspense> </HydrationBoundary>

규칙

useSuspenseQuery 또는 useSuspenseQueries를 사용하는 컴포넌트는 반드시 상위에 <Suspense> boundary가 있어야 한다.

이건 React 공식 규칙이지만, SSR + HydrationBoundary 조합에서 놓치기 쉽다. SSR prefetch가 데이터를 미리 채워주면 개발 중에는 문제가 안 보이다가, 캐시 미스/만료/첫 로드 시에만 터진다.

관련 파일

  • apps/shop/app/[[...slug]]/page.tsx — Suspense 추가
  • apps/shop/src/features/resource-loader/hooks.tsuseResourcesData (useSuspenseQueries 사용)
  • apps/shop/src/shared/providers/PageRenderer.tsxuseResourcesData 호출