Next.js 애플리케이션을 개발하다 보면 다음과 같은 경고 메시지를 만날 수 있습니다:

⨯ useSearchParams() should be wrapped in a suspense boundary at page "/cases". 
Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout



이 경고는 무엇을 의미하며, 왜 Suspense로 감싸야 하는지 알아보겠습니다.

useSearchParams()와 Suspense 경계

 

문제의 원인

Next.js 13 이상에서는 App Router를 사용할 때 기본적으로 서버 컴포넌트를 사용합니다. 

그러나 `useSearchParams()`와 같은 클라이언트 훅을 사용하면 해당 컴포넌트는 클라이언트 컴포넌트로 전환됩니다.

이 과정에서 두 가지 문제가 발생합니다:

1. **하이드레이션 불일치(Hydration Mismatch)**: 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 컴포넌트 간의 차이가 발생할 수 있습니다.

2. **초기 로딩 지연**: 클라이언트 사이드 렌더링으로 전환되면서 페이지 로딩 시간이 길어질 수 있습니다.

 

Suspense의 역할

`Suspense`는 React의 기능으로, 컴포넌트가 로딩되는 동안 대체 UI를 보여줄 수 있게 해줍니다. Next.js에서는 이를 활용하여:

1. **점진적 렌더링**: 페이지의 일부분만 클라이언트 사이드에서 렌더링하고, 나머지는 서버에서 렌더링할 수 있습니다.

2. **로딩 상태 관리**: 클라이언트 컴포넌트가 로딩되는 동안 사용자에게 로딩 UI를 보여줄 수 있습니다.

3. **스트리밍 렌더링**: 서버에서 HTML을 점진적으로 스트리밍하여 사용자 경험을 개선할 수 있습니다.

 

해결 방법

1. 컴포넌트를 Suspense로 감싸기

import { Suspense } from 'react';
import CasesListBox from './CasesListBox';

const CasesListContainer = () => {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <CasesListBox />
    </Suspense>
  );
};

export default CasesListContainer;



2. 페이지 레벨에서 Suspense 적용하기

// app/cases/page.tsx
import { Suspense } from 'react';
import CasesListContainer from '@/components/cases/CasesListContainer';

export default function CasesPage() {
  return (
    <Suspense fallback={<div>페이지 로딩 중...</div>}>
      <CasesListContainer />
    </Suspense>
  );
}

 

Suspense를 사용해야 하는 이유

1. **성능 최적화**: 전체 페이지가 아닌 필요한 부분만 클라이언트에서 렌더링하여 초기 로딩 시간을 단축합니다.

2. **사용자 경험 개선**: 로딩 상태를 명시적으로 보여주어 사용자가 페이지가 로드 중임을 인지할 수 있습니다.

3. **서버 컴포넌트 활용**: 가능한 많은 부분을 서버 컴포넌트로 유지하여 번들 크기를 줄이고 성능을 향상시킵니다.

4. **스트리밍 렌더링**: 서버에서 HTML을 점진적으로 스트리밍하여 Time to First Byte(TTFB)를 개선합니다.

 

실제 구현 예시

// 잘못된 방식
function BadComponent() {
  const searchParams = useSearchParams(); // 클라이언트 훅
  // ... 컴포넌트 로직
}

// 올바른 방식
function GoodImplementation() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <ClientComponent />
    </Suspense>
  );
}

// 클라이언트 컴포넌트
'use client';
function ClientComponent() {
  const searchParams = useSearchParams();
  // ... 컴포넌트 로직
}

결론

Next.js의 App Router에서 `useSearchParams()`와 같은 클라이언트 훅을 사용할 때는 반드시 Suspense 경계로 감싸야 합니다. 

이는 하이드레이션 불일치를 방지하고, 성능을 최적화하며, 사용자 경험을 개선하는 데 도움이 됩니다. 

또한 서버 컴포넌트와 클라이언트 컴포넌트를 효과적으로 분리하여 Next.js의 장점을 최대한 활용할 수 있게 해줍니다.