⚠️

Next.jsでuseSearchParams()を使うときにSuspenseで囲まないとビルドエラーになる理由

に公開

useSearchParams()を使ったらエラーが出る

❌ Missing Suspense boundary with useSearchParams

このビルドエラーは、Next.jsでuseSearchParams()などを使用しているファイルを<Suspense>で囲っていないことが原因で起こります。
下記のコードのように<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>
  )
}

Suspenseコンポーネントとは?

React公式で以下のように書かれていました。

<Suspense> を使うことで、子要素が読み込みを完了するまでフォールバックを表示させることができます。

SuspenseはNext.jsではなくReactの機能で、データが揃っていない間、うまく“待つ”仕組みを React 側に取り込むため作られました。
実際に使うと下記のように書けます。

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

fallbackについてですが、childrenに渡したコンポーネントがまだ読み込みを完了していない場合に、その代わりにレンダーする代替UIです。
つまりローディングスピナやスケルトンのようなものです。

また以下の例では、Albums コンポーネントはアルバムのリストをフェッチする間、サスペンド(一時的にコンポーネントのレンダリングを保留)します。レンダーの準備が整うまで、React は上にある最も近いサスペンスバウンダリ(<Suspense>コンポーネントで囲まれた領域)を、フォールバック(Loading コンポーネント)を表示するように切り替えます。その後データが読み込まれると、React は Loading フォールバックを非表示にし、データとともに Albums コンポーネントをレンダーします。

import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

なぜuseSearchParams()を使っているファイルはSuspenseで囲わないといけないか

結論、個人的な解釈ですが、
useSearchParams() はクライアントコンポーネントでしか使えず、Suspenseで囲まずにこれを使うと、その部分から上位のツリーがすべてクライアントコンポーネント化されてしまい、パフォーマンスが下がるからだと考えています。

Next.js公式で下記のような記述があります。

Why This Error Occurred
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.

訳すと、「サスペンス境界を介さずに検索パラメータを読み込むと、useSearchParams()ページ全体がクライアントサイドレンダリングの対象となります。その結果、クライアントサイドJavaScriptが読み込まれるまでページが空白になる可能性があります。」だそうです。

「useSearchParams()ページ全体がクライアントサイドレンダリングの対象となります。」の部分ですが、useSearchParams()を使っているコンポーネントから見て一番近い<Suspense>コンポーネント(境界)までの範囲が、丸ごとブラウザで動くクライアントコンポーネントの集合として扱われるということです。
だから、useSearchParamsのある部分だけを<Suspense> で囲んで「クライアントで動く範囲」として限定しないと、そこから上位のコンポーネントも全部クライアントで動くことになってしまいます。

ここからは個人的な解釈ですが、Next.jsのApp Routerでは、ページは基本的にサーバーコンポーネントとして静的・SSRレンダリングされます。
ところが useSearchParams() はクライアントコンポーネントでしか使えず、これを使うと、その部分から上位のツリーがすべてクライアントコンポーネント化されてしまいます。
すべてクライアントコンポーネントとするとパフォーマンスが悪いので、Suspenseで囲まないとエラーにしてるんだと思います。(クライアントコンポーネントになると、初期表示が遅くなり、検索エンジンに内容が認識されづらくなる(= SEOに不利)などのデメリットがあります。)

Discussion