React와 Next.js의 Suspense에 대해 알아보자

Published2024.11.25
Read Time8 min read

들어가며

프론트엔드에서 React와 Next.js에서는 비동기 로직과 데이터 페칭을 효율적으로 관리하기 위해 Suspense를 활용한다.

React 18부터 본격적으로 Suspense를 활용하여 관련된 로직을 수행할 수 있는데, React에서 Suspense를 어떻게 사용하는지 알아보고, Next.js에서는 13이상 부터 자동화된 Suspense에 대해, 그리고 이로 인해 발생한 에러와 해결방법에 대해 알아볼 것이다.

React의 Suspense

React의 Suspense는 비동기 로직이 완료될 때까지 대기 상태를 처리한다. 데이터 로딩, 컴포넌트 코드 스플리팅 등을 위해 주로 사용되며, 다음과 같은 방식으로 동작한다

Suspense 기본 동작 (React 18 이상)

  1. Suspense는 컴포넌트 트리의 일부에서 데이터를 로드하거나 리소스를 가져오는 작업이 끝날 때까지 렌더링을 중단한다.
  2. fallback 프로퍼티에 지정된 컴포넌트를 먼저 렌더링한다.
  3. 작업이 완료되면 Suspense 아래의 실제 콘텐츠를 렌더링한다.
import React, { Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./LazyComponent'));

export default function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

이런식으로 명시적으로 Suspense를 배치하여 이를 제어한다.

React.lazy는 컴포넌트를 지연로딩할 때 사용되며 컴포넌트를 처음렌더링 할 때까지 로드하지 않는다.

대신, 해당 컴포넌트가 실제로 화면에 렌더링될 때, 즉 "필요할 때" 로드한다.

이렇게 React.lazy()Suspense를 같이 활용하여 컴포넌트를 지연 로딩하고, 초기 로딩 성능을 개선한다.

Next.js에서 자동화된 Suspense

Next.js 13 이상에서는 App Router를 기반으로 자동 Suspense가 도입되었다. React의 Suspense와 비슷한 기능을 제공하되, 일부 동작이 Next.js에 의해 자동화가 된다.

Next.js 자동 Suspense 동작

  1. 서버 컴포넌트에서 fetch나 비동기 로직을 처리할 때 Suspense가 자동으로 적용된다.
  2. loading.js와 같은 특수 파일을 활용하여 Suspense의 fallback 역할을 수행한다.
  3. 클라이언트 컴포넌트의 경우 별도로 Suspense를 지정해야 동작한다

즉 서버 컴포넌트에서 미리 비동기로 데이터를 가져올 때, 자동으로 지연 로딩이 되고, 로딩이 되는 동안 loading.tsx를 보여줄 수 있는 것 이다.

// app/원하는 경로 페이지/page.tsx (서버 컴포넌트) 
export default async function Page() { 
const data = await fetchData(); 
	// 자동 Suspense 
	return <div>{data}</div>; 
}

위의 코드는 Next.js의 서버 컴포넌트에서 데이터를 서버 단에서 미리 불러 올 때의 예제이다. 만일 loading.tsx를 만들면 이렇게 서버에서 미리 데이터를 불러오는 동안 사용자에게 로딩 중 컴포넌트를 보여줄 수 있다.

// app/원하는-경로-페이지/loading.tsx 
export default function Loading() {
 return <div>Loading...</div>; 
 // 간단한 로딩 상태 
 }

이러한 경우 원하는 경로의 페이지에 접속하는 동안 loading.tsx를 볼 수 있다.

Next.js에서 React의 Suspense를 같이 써보자

Next.js에서 내재되어있는 서버 컴포넌트에서 미리 데이터를 페칭할 때 자동으로 사용되는 Suspense 기능 뿐 아니라, 그 컴포넌트 안에서 react의 Suspense 역시 사용할 수 있다.

다음 예제를 보자.

import { Suspense } from 'react';
import dynamic from 'next/dynamic';
// 비동기 컴포넌트
const LazyComponent = dynamic(() => import('./LazyComponent'), { suspense: true, // Suspense를 사용하려면 이 설정이 필요하다. });

export default function Page() {
const data = await fetchData();
  return (
    <div>
      <h1>Server Side Rendered Data</h1>
      <div>{data}</div>

      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

next/dynamicNext.js에서 제공하는 동적 import 기능이다. 이 기능을 사용하면 클라이언트 사이드에서 컴포넌트를 지연 로딩(Lazy Loading) 할 수 있으며, **서버 사이드 렌더링(SSR)**을 제어할 수 있다.

비동기 로딩을 통해 성능 최적화와 코드 분할을 지원하는 것이다.

next/dynamic에는 여러 옵션이 있는데

  • suspense :React Suspense 기능과 통합하여, 동적 컴포넌트 로딩 중에 fallback UI를 표시할 수 있다.
  • loading:
    const LazyComponent = dynamic(() => import('./LazyComponent'), {
    		loading: () => <div>Loading...</div>, 
    		});
    
    
    이렇게 직접 로딩 컴포넌트를 지정할 수도 있다.
  • ssr: next/dynamic에서는 이 동적 로딩 컴포넌트가 서버 사이드 렌더링에서 제외될 수 있도록 ssr: false 옵션을 사용하여 클라이언트 사이드에서만 컴포넌트를 로드할 수 있게 한다. (이 부분으로 에러를 해결하는 포스트를 추후 쓸 것이다.)

LazyComponent가 볼륨이 높은 페칭 작업을 한다고 가정했을 때 Suspense를 사용하여 데이터를 가져오는 동안 fallback 컴포넌트를 보여줄 수 있다.

즉, 위의 코드에서 Suspense가 어떻게 작동하는 지 정리해보자면

  • 서버 단에서 데이터를 페칭하여 미리가져오고, 그 데이터를 가져오는 시간 동안 loading.tsx의 컴포넌트를 보여준다.
  • 데이터를 가져와서 페이지가 로드된후 Suspense로 감싸준 클라이언트 단의 LazyComponent에서 작업을 하는 동안 fallback 컴포넌트를 보여준다.
  • LazyComponent의 작업이 완료되면 본래 컴포넌트를 보여준다.

마치며

React와 Next.js에서 Suspense를 어떻게 쓸 수 있는지에 대해서, 그리고 next.js에서 두개를 같이 쓰는 예제까지 살펴보았다. 개발 하며 앞으로 보다 나은 사용자 경험과 최적화를 위해 유용하게 사용할 수 있을 듯하다.