Next.js
Next.jsのApp Routerで@k8ordo/stateを使えるようになります。サーバーで読んだ値の渡し方と、Next.jsで変わる点をたどります。
このページの内容
動くもの
app/products/state.tsimport { definePageState } from '@k8ordo/state';
import * as z from 'zod';
export const listState = definePageState('product-list', {
url: z.object({
inStock: z.stringbool().default(false),
page: z.coerce.number().int().min(1).default(1),
}),
});定義とuseAppStateは、Next.jsでもそのまま使えます。entryとlocalStorage、sessionStorageの状態も同じです。Cookieとメモリの状態も変わりません。
Next.jsで違うのは、サーバーでの読み取りとパスの型検査、URLを変えるupdate()の3つです。
サーバーでの読み取り
app/products/page.tsximport Link from 'next/link';
import { Filters } from './filters';
import { listState } from './state';
export default async function Page({
searchParams,
}: PageProps<'/products'>) {
const url = listState.parseUrl(await searchParams);
const products = await fetchProducts(url);
const nextPage = listState.href('/products', {
...url,
page: url.page + 1,
});
return (
<>
<Filters initialUrl={url} />
<ProductList products={products} />
<Link href={nextPage}>Next page</Link>
</>
);
}App RouterのページはsearchParamsを受け取ります。parseUrlに渡すと、urlのスキーマで読んだ値が返ります。欠けたパラメータには既定値が入り、スキーマに合わない値はそのフィールドだけが既定値に戻ります。
parseUrlが受け取る型はUrlInputです。URLSearchParamsか、Record<string, string | string[] | undefined>の形のオブジェクトです。読むときにエラーにはなりません。
hrefは純粋な関数なので、Server Componentの中でもそのまま使えます。
app/products/filters.tsx'use client';
import { useAppState } from '@k8ordo/state';
import type { OutputOf } from '@k8ordo/state';
import { listState } from './state';
type FiltersProps = { initialUrl: OutputOf<typeof listState.url> };
export function Filters({ initialUrl }: FiltersProps) {
const [{ inStock }] = useAppState(listState, { initialUrl });
return (
<form action="/products" method="get">
<label>
<input
defaultChecked={inStock}
name="inStock"
type="checkbox"
value="true"
/>
In stock only
</label>
<button type="submit">Apply</button>
</form>
);
}読んだ値は、initialUrlとしてClient Componentに渡します。サーバーの描画とハイドレーションの描画が、既定値でなくURLの値で行われます。
パスの型検査
types/k8ordo-state.d.tsimport type { Route } from 'next';
declare module '@k8ordo/state' {
interface Register {
path: Route;
}
}Next.jsにはルート表が無いので、nextのRouteをpathに登録します。アプリの中のすべてのhrefが、Next.jsの知らないパスを型エラーにします。
typedRoutesを有効にすると、Routeはアプリのパスのunionになります。hrefの返り値の型はパスをリテラルのまま残すので、Next.jsの<Link>の型検査も通ります。
Registerの拡張はアプリの中でだけ行ってください。共有のライブラリで拡張すると、その制約が使う側のすべてに及びます。
URLを変えるupdate()
Next.jsのルーターは、navigateイベントをintercept()しません。そのため、URLを変えるupdate()が呼ぶnavigation.navigate()は、ページ全体の読み込みになります。
URLの変更は、リンクとGETフォームで行ってください。リンクの作り方はリンクを見てください。History APIで代わりに書く仕組みはありません。
URLを変えないupdate()は影響を受けません。