Slide 1

Slide 1 text

Next.jsでクエリパラメータを楽に扱おう nuqsを紹介! いまいまい 1

Slide 2

Slide 2 text

自己紹介 いまいまい (今井俊希) 株式会社ゆめみ 新卒 フロントエンドエンジニア 本編ではBlockNoteというライブラリで登壇 2

Slide 3

Slide 3 text

URLとクエリパラメータ、舐めたら死ぬ あぶない! Link URLを文字で扱うのは危険と言われがち URLクラスを使う URLSearchParamsクラスを使う 3

Slide 4

Slide 4 text

Next.jsでのクリパラメータでの扱い router.queryを使う const router = useRouter(); // ... router.push({pathname: "hoge", query: {id: router.query.id}}); useSearchParamsを使う const searchParams = useSearchParams(); // ... Link 4

Slide 5

Slide 5 text

クエリパラメータ管理の課題 1. 複数のクエリパラメータを管理したいとき 一部だけ更新して、一部は維持とか 2. stateとの同期 inputなどの状態との同期 queryからstateへの初期状態反映 3. queryのバリデーション 4. AppRouter / ServerComponentsへの対応 5

Slide 6

Slide 6 text

nuqsについて紹介 next-usequerystateの略 発音は“nukes”. [njuks] Next.jsにおけるクエリパラメータを型安全かつ直感的に扱えるようにする 6

Slide 7

Slide 7 text

nuqsの使い方 ボタンの状態をqueryを同期させる inputのstateがリアルタイムにqueryに反映される const [name, setName] = useQueryState('name', parseAsString.withDefault("")) // ... return ( setName(e.target.value)} /> ) 7

Slide 8

Slide 8 text

豊富なparserの種類 string number (integer, float, hex) boolean literal (string, numeral) enum date & timestamp array json (with zod scheme) なんなら自分で作れる (createParser) 8

Slide 9

Slide 9 text

複数のqueryを定義したい場合 それぞれのqueryを非同期に更新できる const [isCheck, setIsCheck] = useQueryState('count', parseAsBoolean.withDefault(false)) const [name, setName] = useQueryState('name', parseAsString) // ... return (
setName(e.target.value)} /> setName(e.target.value)} />
) 9

Slide 10

Slide 10 text

Option パーサーに複数のオプションを渡すことができる parseAsString.withOptions({ history: 'push' }) history // 履歴が置き換えられる useQueryState('foo', { history: 'replace' }) // 新しく履歴が追加される useQueryState('foo', { history: 'push' }) historyのhackはbad UXにつながる可能性があるので注意 10

Slide 11

Slide 11 text

shallow クエリパラメータの更新によるルーティング範囲の切り替え useQueryState('foo', { shallow: true }) true クライアントで完結 false サーバーまで通知されて、場合によっては再レンダリングされる queryによって即座にfetchさせたい場合など ハードナビゲーション 11

Slide 12

Slide 12 text

Serialize nuqsで定義したqueryを型安全にurl文字列で出力できる const searchParams = { search: parseAsString, limit: parseAsInteger, sortBy: parseAsStringLiteral(['asc', 'desc'] as const) } const serialize = createSerializer(searchParams) serialize("/hoge", { search: 'foo bar', limit: 10, sortBy: 'asc' }) // -> /hoge/?search=foo+bar&limit=10&sortBy=asc 検索フォームなどは の関数をLinkコンポーネントで渡すだけでよくなる 12

Slide 13

Slide 13 text

Server Componentsでの利用 1. queryの定義・cacheとserializeの定義をする const searchParams = { q: parseAsString.withDefault(''), maxResults: parseAsInteger.withDefault(10) } export const searchParamsCache = createSearchParamsCache(searchParams) export const serialize = createSerializer(searchParams); 13

Slide 14

Slide 14 text

2. ページロード時のsearchParamsと同期をとる clientでのuseQueryStateのsetを初期で行うイメージ export default function Page({ searchParams }: { searchParams: Record }) { const searchParamsCache.parse(searchParams) // ... } 14

Slide 15

Slide 15 text

3. cacheは .all または .get で取得可能 ナビゲーションによってクエリのcacheが更新される const { q, maxResults } = searchParamsCache.all(); const setQuery = (query: string) => { return serialize("/", { q: query, maxResults }); }; return ( Link ) stateとのリアルタイムな同期は流石にClient Componentが必要 ただしsearchParamsCacheとuseQueryStateとの連携は可能 15

Slide 16

Slide 16 text

蛇足 Next.jsがソフトナビゲーションにqueryの更新ができる コンポーネントを作 っているらしい import Form from 'next/form' export default function Page() { return ( Submit ) } Components: | Next.js 16

Slide 17

Slide 17 text

良いqueryライフを! 17