@k8ordo/router

パスを型で確かめる

リンクのパスは文字列なので、書き間違えてもそのままでは気づけません。このページでは、パターンやparamの誤りを型エラーにする方法と、その検査を自分のコードや@k8ordo/stateで使う方法を説明します。

このページの内容

paramは設定なしで確かめられる

hrefやnavigateTo、useParamsは、渡したパターンの文字列からparamの型を作ります。そのため、何も設定しなくても、paramの渡し忘れや名前の書き間違いは型エラーになります。

ts
href('/products/:id', { id: '42' });
href('/products/:id');
idが無いので型エラーになる
href('/products/:id', { productId: '42' });
productIdはパターンに無いので型エラーになる

パターンをルート表と照らし合わせる

paramとは違って、パターンそのものの書き間違いは、何も設定しないと通ってしまいます。ルート表をRegisterに登録すると、表に無いパターンも型エラーになります。

src/k8ordo-router.d.ts
import type { routes } from './routes';

declare module '@k8ordo/router' {
  interface Register {
    routes: typeof routes;
  }
}
ts
href('/products/:id', { id: '42' });
href('/prodcuts/:id', { id: '42' });
表に無いパターンなので型エラーになる

登録の前は、/で始まる文字列ならどれでも通ります。登録したあとは、hrefとnavigateToはリンク先にできるパターンだけを、useParamsは表のパターンだけを受け付けます。useMatchとmatchPathは、表のパターンと、その後ろに/*を付けたものを受け付けます。

登録はアプリの中で1度だけ行います。ライブラリで登録すると、そのライブラリを使うすべてのアプリに、自分のルート表を押し付けることになるからです。

情報

メモ

@k8ordo/staticと@k8ordo/serverでは、この登録が.k8ordo/register.gen.tsに生成されます。自分で書くと同じ登録が2つになるので、書かないでください。

自分のコードでパターンの型を使う

ルート表から作った型は、自分のコードでも使えます。たとえば、ナビゲーションに並べる行き先を、表にあるページに限りたいときです。

src/links.ts
import type { RegisteredNavigablePattern } from '@k8ordo/router';

export type SitePath = Exclude<
  Extract<RegisteredNavigablePattern, `/:locale${string}`>,
  `/:locale${string}:${string}`
>;

これはこのサイトのSitePathです。リンク先にできるパターンのうち、/:localeで始まり、ロケールのほかにparamを持たないものに絞っています。ナビゲーションのデータに表に無いページを書くと、型エラーになります。

登録した表から作られる型は、次の4つです。

  • RegisteredPattern:表のすべてのページのパターン(/*のパターンも含む)
  • RegisteredNavigablePattern:リンク先にできるパターン(/*を含むものを除く)
  • RegisteredParams<P>:パターンPへのリンクが受け取るparams
  • RegisteredPageParams<P>:パターンPのページが受け取るparams

登録の前は、はじめの2つは/で始まる任意の文字列になります。

情報

メモ

フレームワークの下でparamsSchemaを書いたページでは、リンクもページも、paramをスキーマが作った型で扱います。詳しくは「フレームワークの下で使う」で説明します。

@k8ordo/stateのリンクも同じ表で確かめる

@k8ordo/stateのRegisterにも、同じ1行を書けます。こうすると、@k8ordo/stateのhrefに渡すパスも、このルーターと同じルート表で確かめられます。

src/k8ordo.d.ts
import type { routes } from './routes';

declare module '@k8ordo/router' {
  interface Register {
    routes: typeof routes;
  }
}

declare module '@k8ordo/state' {
  interface Register {
    routes: typeof routes;
  }
}
ts
listState.href('/products', { q: 'lamp' }); // '/products?q=lamp'
listState.href('/prodcuts', { q: 'lamp' });
表のどのパターンにも合わないので型エラーになる

@k8ordo/stateのhrefが受け取るのは、パターンではなく実際のパスです。/products/42のように、paramの位置にも値を書きます。このパスがどれかのパターンに合うかを、区間ごとに確かめています。

@k8ordo/stateが使っている検査は、NavigablePathという型です。ほかにパスを受け取るものがあれば、同じ型で確かめられます。

ts
import type { NavigablePath } from '@k8ordo/router';

type A = NavigablePath<typeof routes, '/products/42'>;
// '/products/42'
type B = NavigablePath<typeof routes, '/products/42/reviews'>;
// never
type C = NavigablePath<typeof routes, '/products/'>;
// never

NavigablePathは、パスがリンク先にできるパターンのどれかに合えばそのパスを、どれにも合わなければneverを返します。:idのようなparamの位置には、空でない1区間ならどれでも入ります。

末尾にスラッシュのあるパスは受け付けません。照合では/products/も/productsと同じページになりますが、リンクに書くパスは1通りの書き方にそろえるためです。

表のすべてのパスを並べた型を作って比べないのは、/:localeのようなページがあると、その型が/${string}になってしまうからです。/${string}は、どんなパスでも受け付けてしまいます。

k8ordo

Baselineに入った機能を、制限なく使うReactのライブラリ群。

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2