Next.js の検索(useSearchParams)。最小構成サンプル
検索でようやく Client Component が1つ必要になります。
動いているものを触る/demo/searchファイル構成
並んでいるのは、上の画面をいま動かしている本物のファイルです。名前を押すと中身が出ます。
- app/
全ページの外側。本物のプロジェクトでは html と body をここで書きます
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "検索の最小構成",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return <main>{children}</main>;
}- .tsx
- 画面を返すファイル。HTML のような書き方ができます
- .ts
- 画面を返さないファイル。処理とデータだけを置きます。ここに画面を書くと原因を名指ししないエラーで落ちます
- Build
- ビルド時に一度だけ動きます
- Server
- アクセスのたびにサーバーで動きます
- Browser
- 利用者の手元で動きます
ここで初めて1つ要る
入出力もログインも一覧と詳細も、Client Component は1つも要りませんでした。検索で初めて1つ必要になります。
必要になる理由は、打つたびに反応させたいからです。送信ボタンを押す形でよければ、ここも素のフォームで足ります。
// Client Component は要らない <form> <input name="q" /> <button type="submit">探す</button> </form>
ブラウザへ送るコードは増えない。押すまで結果は変わらない。
// 入力欄だけが Client Component になる 'use client' export function SearchBox({ value }) { const router = useRouter() return <input defaultValue={value} onChange={...} /> }
入力欄のぶんだけコードが増える。打つたびに結果が変わる。
増やすのは葉だけ
越えられる: 検索語(アドレスとして)
越えられる: 絞った結果
越えられない: 元のデータ
越えられない: 絞り込みの処理
実機で確かめました。ビルドしたものを検査すると、元のデータは配信される JavaScript に含まれていません。含まれていたのは、入力欄に書いた placeholder の文字だけでした。境界がそのまま成果物に出ています。
絞り込みはサーバーでやる
入力欄がやっているのは、アドレスを書き換えることだけです。
router.replace(q ? `?q=${encodeURIComponent(q)}` : '?')
絞り込みそのものは、サーバー側のページがやります。
const { q } = await searchParams
const query = String(q ?? '').slice(0, 40)
const found = await searchTerms(query)
実機で確かめました。何も入れなければ 8 件、use で 5 件、server で 2 件、zzz で 0 件になります。元の8件はブラウザへ行っていません。
- Browserアドレスを書き換える
入力欄がやるのはここまで。絞り込みはしない。
- Server検索語を受け取って絞る
元のデータはここにある。絞ってから返す。
- Browser結果だけが差し替わる
入力欄は入れ替わらないので、打った文字は消えない。
読むのはサーバーの仕事
useSearchParams を使えば、ブラウザ側でも検索語を読めます。このサンプルでは使っていません。
読むのはサーバーがもう済ませていて、入力欄には切ったあとの値を props で渡しています。ブラウザ側でもう一度アドレスを読むと、切る前の値を使うことになります。
Client Component が要る理由は、読むためではなく書き換えるためです。打つたびにアドレスを変えるのは、ブラウザ側にしかできません。
それでも useSearchParams を使うなら
「Suspense で囲め」と書かれているのを見たことがあるかもしれません。囲めと言われるのは、そのページが静的なままのときです。
ビルド時にページを作ろうとしたとき、検索語はまだ存在しません。そこで手が止まるので、Next.js は待つ場所を要求します。それが Suspense です。
このページのように searchParams を自分で読んでいるページは、その時点でもう動的なので要求されません。実機で確かめました。
囲まずにビルドが落ちる形と、その文言はこの問題にあります。 「とりあえず囲む」ではなく、「そのページが静的か動的か」で決めてください。
ここから足していくとき
- 入力を間引く。いまは1文字ごとにサーバーへ行きます。実務では少し待ってから送ります
- 見つからなかったときの案内。0件のときに何を出すかは、検索の質そのものです
- データベースへ。
searchTermsの中を置き換えるだけで、ページ側は変わりません
変える場所はいつも app/data.ts の中か、切り出した葉の部分です。ページを Client Component にする必要は、ここでも出てきません。
入れていないもの
このまま本番に持っていかないでください。省いてあるのは次の点です。
- 入力の間引き。1文字ごとにサーバーへ行くので、実務では待ち時間を入れます
- 検索語の細かい検証。長さを40文字で切っているだけです
- データベース。8件の配列を絞っているだけです
- 件数が多いときのページ送りと、見つからなかったときの案内
- 検索語の記録。何が探されたかは残していません
出典
- nextjs.org/docs/app/api-reference/functions/use-search-params
- nextjs.org/docs/app/api-reference/functions/use-router
- nextjs.org/docs/app/api-reference/directives/use-client
- nextjs.org/docs/messages/missing-suspense-with-csr-bailout
このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。