Suspense 알아보기, 그리고 Next.js, useSearchParams()

2cham_ny·2024년 4월 17일
7

TIL

목록 보기
5/8

01 - WHAT

Routing: Loading UI and Streaming

Suspense – React

올해 초, 사이드 프로젝트를 하던 도중 에러가 생겨 Suspense로 해결했던 경험이 있었는데.. 오늘은 이 Suspense에 대해 톺아보려고 한다.

우선 Suspense는 비동기 작업을 더욱 더 효율적으로 처리할 수 있게 해주고 사용자 경험을 향상시키는 데에 도움을 준다. 우선 공식 문서를 보자!

<Suspense>

lets you display a fallback until its children have finished loading.

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

리액트 공식 문서의 최상단에 있는 내용을 먼저 보자.

내용을 정리하면 아래와 같다.

  • children : 렌더링하려는 실제 UI
  • fallback : 로드가 완료되지 않은 경우 실제 UI 대신 렌더링할 대체 UI

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

  1. Suspense mount
  2. MainComponent mount
  3. MainComponent에서 useSuspenseQuery 훅을 사용하여 비동기 데이터 요청
  4. MainComponent unmount, fallback UI인 Loader mount
  5. 비동기 데이터 요청이 완료되면 fallback UI인 Loader unmount
  6. MainComponent mount
  • ErrorBoundary는 에러가 발생했을 때 보여주는 Fallback UI를 선언적으로 작성할 수 있고, 리액트 쿼리는 Suspense와도 결합해서 서버 통신 상태가 로딩 중일 때 Fallback UI를 보여줄 수 있게 선언적으로 작성할 수 있다.
  • 참고로, Suspense 컴포넌트는 리액트 v16부터 제공되는 Component Lazy Loading이나 Data Fetching 등의 비동기 처리를 할 때, 응답을 기다리는 동안 Fallback UI(ex: Loader)를 보여주는 기능을 하는 컴포넌트이다.

❓Suspense의 장점은?

  • 더 나은 코드 구조
    • 비동기적인 코드를 동기적인 방식으로 작성할 수 있다는 것
    • 여러 개의 컴포넌트 중 특정 컴포넌트에 대한 로딩상태 관리에 대해 걱정하지 않고 쉽게 코드를 작성할 수 있게 된다.
    • 데이터 표시의 순서에 상관없이 병렬적으로 데이터를 패칭할 수 있다.
  • 사용자 경험 개선
    • 유저가 데이터가 로드되기를 기다리는 동안 로딩 상태를 시각적으로 표시할 수 있다.
    • 애플리케이션의 응답성이 향상된다.

02 - HOW

  • 문제 상황

    • 페이지에서 컴포넌트를 불러오려고 했는데 에러가 나서 로 해결했던 경험이 있음
    • 자세히 파헤쳐보지 않고 급한대로 해결하고 넘어갔던 기억
  • 개선 방법

    • 공식 문서랑 깃허브를 참고하였다.

내가 이 에러를 어떻게 해결했냐면…😇

우선 내 에러는 아래와 같았다.

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>
  )
}

useSearchParams() should be wrapped in a suspense boundary at page "/404". · vercel next.js · Discussion #61654

위 깃허브는 이와 관련한 여러 이슈들을 다루고 있는데, 난 여기서 좀 도움을 받았던 것 같다.

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에서 렌더링하려고 하기 때문

음… 이렇게 정리할 수 있을 것 같은데 다시 한번 흐름을 정리해보는 게 좋을 것 같아 아래와 같이 작성해보았다.


📌 플로우

  1. useSearchParams()를 사용할 땐, 넥스트에서 CSR을 하게 되는데, 이러한 경우 클라이언트 측에서 데이터를 가져오기 위해 Suspense가 필요하다.
  2. 넥스트에선 기본적으로 Error Boundaries를 사용하여 비동기 작업에서 발생하는 오류를 처리한다. 그래서 CSR을 수행할 때, Suspense나 코드 스플리팅이 적용되지 않아서 문제가 발생한 것.
  3. 코드 스플리팅된 컴포넌트를 로딩하는 동안에 로딩중임을 표시할 수 있어야 해서, Suspense를 적용한 것

⇒ 즉, Next.js에서는 useSearchParams()를 사용할 때 클라이언트 측에서 비동기 작업을 수행하고 결과를 기다리는 동안에는 Suspense를 적용하여 로딩 상태를 표시하는 것이 좋다고 한다.


03 - RETROSPECT

늦게라도 Suspense에 대해 알아볼 수 있어서 참.. 다행이다!

프로젝트 진행 도중에는 데드라인에 맞추느라 에러 해결하는 데에만 초점을 맞춰서 깊게 공부하지 못했었는데, 다행히 이번 TIL로 정리해볼 수 있어서 많은 도움이 된 것 같다.

번외로 영어 공부도 열심히 해야겠다….

04 - 오늘의 한마디

오늘 마신 아이스 아메리카노가 유난히 맛있다

오늘은 한 잔만 마시자 부디!

profile
😈 기록하며 성장하자!

0개의 댓글