Routing: Loading UI and Streaming
올해 초, 사이드 프로젝트를 하던 도중 에러가 생겨 Suspense
로 해결했던 경험이 있었는데.. 오늘은 이 Suspense에 대해 톺아보려고 한다.
우선 Suspense는 비동기 작업을 더욱 더 효율적으로 처리할 수 있게 해주고 사용자 경험을 향상시키는 데에 도움을 준다. 우선 공식 문서를 보자!
<Suspense>
lets you display a fallback until its children have finished loading.
<Suspense fallback={<Loading />}> <SomeComponent /> </Suspense>
리액트 공식 문서의 최상단에 있는 내용을 먼저 보자.
내용을 정리하면 아래와 같다.
Suspense를 사용하면 데이터 로딩이 완료된 후 본 렌더링을 시작하는 게 아니라, 데이터 로딩과 함께 본 렌더링을 시작할 수 있다.
즉, Suspense는 컴포넌트가 비동기 데이터를 필요로 하는 상황을 ‘일시중단’시킬 수 있게 도와주고, 컴포넌트가 데이터를 기다리는 동안 다른 작업을 수행하거나 로딩중과 같은 표시를 할 수 있다.
기본적으로 아래와 같이 사용할 수 있다. 참고로 Tanstack-Query v5 버전을 기준으로 코드를 작성하였다.
import { useSuspenseQuery } from '@tanstack/react-query';
function TestComponent() {
const { data, error, isLoading } = useSuspenseQuery({
queryKey: ["fetchData"],
queryFn: () => getFetchData(),
});
return { data, error, isLoading };
}
function App() {
return (
<Suspense fallback={<Loading />}>
<TestComponent/>
</Suspense>
);
}
위 코드에서 TestComponent는 useSuspenseQuery
훅을 사용해 데이터를 불러온 뒤, Suspense와 호환됨으로써 데이터를 기다리는 동안 자동으로 Loading 컴포넌트를 보여주게 된다.
useSuspenseQuery와 결합한 Suspense는 다음과 같은 과정으로 동작한다고 한다.
✍️ Flow of useSuspenseQuery and Suspense
- ErrorBoundary는 에러가 발생했을 때 보여주는 Fallback UI를
선언적
으로 작성할 수 있고, 리액트 쿼리는Suspense
와도 결합해서서버 통신 상태가 로딩 중
일 때 Fallback UI를 보여줄 수 있게 선언적으로 작성할 수 있다.- 참고로, Suspense 컴포넌트는 리액트 v16부터 제공되는
Component Lazy Loading
이나Data Fetching
등의 비동기 처리를 할 때, 응답을 기다리는 동안 Fallback UI(ex: Loader)를 보여주는 기능을 하는 컴포넌트이다.
❓Suspense의 장점은?
문제 상황
개선 방법
내가 이 에러를 어떻게 해결했냐면…😇
우선 내 에러는 아래와 같았다.
useSearchParams() should be wrapped in a suspense...
친절하게 콘솔에 해결 방법까지 알려주다니..
Missing Suspense boundary with useSearchParams
우선 공식 문서에서는 다음과 같이 나와있다.
Reading search parameters through
useSearchParams()
without a Suspense boundary will opt the entire page into client-side rendering. This could cause your page to be blank until the client-side JavaScript has loaded.
이를 해석하면 다음과 같다.
⇒ Suspense boundary 없이 useSearchParams()
를 통해 검색 파라미터를 읽으면, 전체 페이지가 클라이언트 사이드 렌더링을 선택하게 된다. 이로 인해 클라이언트 측 자바스크립트가 로드될 때까지 페이지가 비어있을 수 있다.
그래서 Suspense를 넣어주게 되면, 클라이언트 사이드 렌더링으로 인식하게 된다.
'use client'
import { useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
function Search() {
const searchParams = useSearchParams()
return <input placeholder="Search..." />
}
export function Searchbar() {
return (
// You could have a loading skeleton as the `fallback` too
<Suspense>
<Search />
</Suspense>
)
}
위 깃허브는 이와 관련한 여러 이슈들을 다루고 있는데, 난 여기서 좀 도움을 받았던 것 같다.
icyJoseph씨… 감사합니다!
When React encounters the usage of useSearchParams, it will suspend the rendering up to the closest Suspense boundary, which means that your page will show that Suspense boundary as fallback. This is not optimal. Hardly 100% of your page's content depends on the useSearchParams hook, and you could send that content to the client already. The right solution is to figure out where exactly this is happening, contain it and put a Suspense boundary around it…
⇒ React가 useSearchParams의 사용을 발견하면, 가장 가까운 Suspense 경계까지 렌더링을 일시 중단한다고 한다. 이는 페이지가 Suspense 경계를 fallback으로 표시한다는 의미이다.
⇒ useSearchParams()를 사용하면 클라이언트 사이드 측에서 정적 렌더링 동안에 가장 가까운 Suspense boundary에서 렌더링하려고 하기 때문
음… 이렇게 정리할 수 있을 것 같은데 다시 한번 흐름을 정리해보는 게 좋을 것 같아 아래와 같이 작성해보았다.
📌 플로우
useSearchParams()
를 사용할 땐, 넥스트에서 CSR을 하게 되는데, 이러한 경우 클라이언트 측에서 데이터를 가져오기 위해 Suspense
가 필요하다.⇒ 즉, Next.js에서는 useSearchParams()를 사용할 때 클라이언트 측에서 비동기 작업을 수행하고 결과를 기다리는 동안에는 Suspense를 적용하여 로딩 상태를 표시하는 것이 좋다고 한다.
늦게라도 Suspense에 대해 알아볼 수 있어서 참.. 다행이다!
프로젝트 진행 도중에는 데드라인에 맞추느라 에러 해결하는 데에만 초점을 맞춰서 깊게 공부하지 못했었는데, 다행히 이번 TIL로 정리해볼 수 있어서 많은 도움이 된 것 같다.
번외로 영어 공부도 열심히 해야겠다….
오늘 마신 아이스 아메리카노가 유난히 맛있다
오늘은 한 잔만 마시자 부디!