도입 배경

첫번째 문제 상황 - SSR 에러

Suspense 가 동작하려면 화면 렌더링 중 promise가 발생해야지만 fallback UI를 보여주게 된다.

그러므로 일반적인 쿼리가 아닌 useSuspenseInfiniteQuery 를 사용하여 데이터 패칭을 진행했다.

비교 항목 useQuery (명령적) useSuspenseQuery (선언적)
데이터 타입 `T undefined` (체크 필요)
로딩 처리 if (isLoading) 분기 처리 직접 작성 상위 <Suspense> 컴포넌트가 위임받음
에러 처리 if (isError) 분기 처리 직접 작성 상위 <ErrorBoundary>가 위임받음
핵심 메커니즘 상태(State) 기반 렌더링 Promise Throwing 기반 렌더링
비교 항목 useInfiniteQuery (기존) useSuspenseInfiniteQuery (도입)
렌더링 방식 데이터 유무와 상관없이 즉시 렌더링 데이터가 올 때까지 렌더링을 일시 중단
데이터 타입 dataundefined일 수 있음 (Optional) data항상 존재함을 보장 (Required)
로딩 상태 관리 if (isLoading) 등 조건부 렌더링 필요 상위 <Suspense> 가 전담 (로직 분리)
에러 상태 관리 if (isError) 등 조건부 렌더링 필요 상위 <ErrorBoundary> 가 전담
컴포넌트 책임 데이터 상태에 따른 UI 분기 책임까지 가짐 오직 데이터 출력에만 집중 (관심사 분리)

이때 컴포넌트는 use client + client fetch 로 작성을 했는데 서버환경에서 실행되어 에러가 발생했다.

TypeError: Failed to parse URL

패치 경로가 상대경로가 아닌 절대경로여야 하는 에러가 발생했다

그러면 Suspense 쿼리가 어떻게 동작하기에 서버에서 실행이 되는가?

Promise Throwing과 렌더링 블로킹

Suspense 쿼리는 쿼리에 데이터가 없으면 Promise를 Throw 한다 React 는 이 Promise를 캐치하여 컴포넌트 렌더링을 일시중단 상태로 만들고 가장 가까운 Suspense boundaryfallback 을 보여준다. 이때 서버에서도 해당 로직이 작동하여 데이터를 다 채운뒤 완성된 HTML을 전달하려고 시도한다. Promise가 resolve되면 React는 중단됐던 지점부터 다시 렌더링을 시작한다.

즉 Hydration을 보장한다.

이런 매커니즘으로 인해 클라이언트 패치로 작성 했지만 서버 환경에서 실행 될 수 있었던 것이다.

  1. useSuspenseInfiniteQuery는 기본적으로 이 데이터 없이는 렌더링 안한다고 선언
  2. Next 는 SSR을 지원하기 때문에 서버에서 미리 받아오기위해 fetch 진행
  3. 이때 클라이언트 패치는 프록시를 거쳐야하기 때문에 상대경로를 사용하고 있는데 서버에는 그런경로가 존재하지 않기 때문에 파싱에러가 터짐
참고 : 브라우저는 `window.location`를 통해 상대 경로의 기준점을 찾을 수 있어 
현재 도메인을 자동으로 붙여주지만 서버환경에서는 기준 도메인을 찾을 수 없기 때문에
`fetch` 호출 시 `TypeError: Failed to parse URL`이 발생한다.

그럼 여기서 궁금한게 발생한다.