Next.jsのリンクを型安全に生成する
動機
Next.jsに限らず、リンク生成って結局文字列生成なので開発中にタイポしたりロジックミスって404なったりっていうのがずっといやで、型安全にリンク生成をしたいなぁと思ってました。
そこで、Next.js専用ですが型安全にリンク生成できるライブラリを作りました。
欲しかったもの
- Nextの内部リンクを表現した型
- Nextのパラメータや、付与されうるGetパラメータを表現した型
- 生成されるリンクの整合性を簡単にテストできる
使い方
$ npm install next-type-link-gen
import {
dynamicRoute,
nextLinksHooksFactory,
staticRoute,
} from 'next-type-link-gen'
const useNextLinks = nextLinksHooksFactory({
top: staticRoute('/'),
name: dynamicRoute<{
name: string
a?: string
b?: number
}>('/[name]'),
})
const YourLink: React.FC = () => {
const links = useNextLinks()
return (
<div>
<Link
// href: /
href={links.top.toUrl()}
// if `pathname` in next/router match, true
current={links.top.isCurrent()}
>
Top
</Link>
<Link
// href: /fake_name?a=aaa&b=999
href={links.name.toUrl({
name: 'fake_name',
a: 'aaa',
b: 999,
})}
// if `pathname` in next/router match, true
current={links.name.isCurrent()}
>
User top
</Link>
</div>
)
}
ルーティングの正統性をテストする
readNextPagesRoute
で/src/pages
もしくは/pages
のパスを解析してrouter.pathname
に入りうる値を全部出します。
これと作ったリンクが部分一致すれば、そのルーティングは安全と言い切れるかと思います。
もちろん、全部一致まで見れば網羅性も担保できます。
import { renderHook } from '@testing-library/react-hooks'
import { linksMapToRouteString, readNextPagesRoute } from 'next-type-link-gen/utils'
import { useNextLinks } from './url'
test('[useNextLinks] routes', () => {
const { result } = renderHook(useNextLinks)
const pageUrls = readNextPagesRoute()
const routeUrls = linksMapToRouteString(result.current)
expect(pageUrls).toEqual(expect.arrayContaining(routeUrls))
})
制作裏話
本当はwebpackに介入しようとしてた
当初はAPIとしては自分で内部リンクを定義するのではなく、勝手にリンク生成してくれる方がいいなって思ってたので、webpackに介入して常に勝手に最新のディレクトリ構造を解析しようと思ってました。
これを断念した理由としては、
- ファイル数がめちゃくちゃ多い時に開発中のbuildが遅くなる可能性がある(未検証、ちゃんとやればそうでもないかも)
- Getパラメータとかの型表現もしたかった
ので、諦めようと思いCLIも検討しました。
ただCLIで作成したものを修正できないっていうのもなんかなぁって思い、自前定義+テストUtilityの方向へと思想が流れていきました。
いろんな理由でローカルテストがしづらかった
ローカルテスト時に相対パスでライブラリをpackage.jsonに書いてたら、peerDependenciesがうまく読み込めなかったりwebpackのeval内でエラー吐いてたりでうまく動きませんでした。
やりよう他にもいっぱいありそうだけど、ライブラリ自体はそこまで大きくなかったのもあってまめにリリースして挙動確認するっていう荒技で進めました。
もうちょっといい方法あった気がするなぁ。。。
tscのmoduleとかtargetに苦戦した
NextはUniversalなJSなのでumd
とか指定するもんだと思い込んでて(あの辺いまでも理解
半ば)、runtimeエラーになるわ前述の問題でうまくimportできないわで結構詰まりました。。
とはいえ作ってよかった
学びも多かったし、パッケージ公開したのは2回目なんでいい経験になったなと。
何よりもともと作ってたNextの方のソースがすっきりしました。
まとめ
ざっと作って公開したけど挙動とかあんま動作環境とか網羅してないんで、何かありましたらぜひissue立ててください🙇♂️
Discussion