useSearchParams を Suspense で囲まないと止まる
実際に出るエラー
useSearchParams を Suspense で囲まずに使ったときに出るビルドエラーです。
何が決まっていないのか
'use client'
import { useSearchParams } from 'next/navigation'
export default function Find() {
const q = useSearchParams().get('q')
return <p>{q}</p>
}
useSearchParams() should be wrapped in a suspense boundary at page "/find".
use client を付けてあるのに止まります。ここが分かりにくいところです。
use client を付けても、ビルド時に一度は HTML を作ります。そのとき ?q=... に何が入るかは決まっていません。何を描けばよいか決まらないので、そこで止まります。
直し方は囲うこと。設定ではない
読む部分だけを切り出して Suspense で囲みます。
import { Suspense } from 'react'
import { SearchBox } from './SearchBox'
export default function Find() {
return (
<Suspense fallback={<p>読み込み中です</p>}>
<SearchBox />
</Suspense>
)
}
実機で確かめました。これでビルドは通り、しかもページは事前に描かれたまま残ります。
Route (app)
└ ○ /find ← 静的のまま
出来あがった HTML には 読み込み中です が入っていました。ブラウザに届いたあと、URL を読んで中身が置き換わります。
似ているが結論が逆の話
「Suspense で囲めば静的なまま」は、いつでも成り立つわけではありません。
| 囲った中で読むもの | 読む場所 | 外側 |
|---|---|---|
| useSearchParams | ブラウザ | 静的のまま |
| cookies() | サーバー | ルート全体が動的になる |
cookies() はサーバーが要求を受け取って初めて読めます。だから1か所でも読むと、ルート全体がアクセスのたびに描かれる側へ移ります(この問題で実機の結果を出しています)。
useSearchParams はブラウザに届いたあとに読みます。サーバーは何も待たなくてよいので、外側は事前に描けます。
実際に動いているもの
このサイトの検索の最小構成は、この形で動いています。説明用に書き直したコードではなく、そのページを動かしている本物のファイルです。
出る条件をまとめると
useSearchParamsを使っている- そのページが事前に描かれる対象になっている
- 読む部分が
Suspenseで囲まれていない
3つ揃ったときだけ出ます。直すのは3つめです。
出典
- nextjs.org/docs/messages/missing-suspense-with-csr-bailout
- nextjs.org/docs/app/api-reference/functions/use-search-params
このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。