NextJS から Remix への移行記録
すでに7割ぐらい移行完了してるけれど、色々苦戦したりした部分もあるので思い出しながら書いていきたい
移行作業してるのは今作ってるSNS
また、環境自体は最近はやり?の Makefile + Docker で全て構築している。
NextJSからの移行を決断した理由はだいたい issue に書き残してある
色々書いてるので再整理しつつクリティカルな部分を書き起こすと
- デザイナー専門がチームにいないのでMUI等のUIライブラリ(= css in js )に頼る部分が多く、App Routerのメリットがあまり受けられそうにない
- メリットが薄いのに頑張って use client を書き続けるのに疲れた
というあたり
これに加え、フロントの開発がかなり先行しバックエンドとの統合待ちが発生し、私のリソースを移行作業に割く余裕が十分ある、という内部事情があった
やめたいのは NextJS ではなく App Router な部分はあるので Pages Router にするという手もなくはなかった。
しかし今後 NextJS が力を入れていくのは App Router のみだろうし、将来的にまた移行作業をさせられる可能性が高い。
近い未来に負債化が確定している時代逆行の移植は不毛すぎるので Remix への移行とした
移行時点で動いていたのは以下
- NextJS
- Ladle
- Vitest
- Playwright(スナップショットのみ)
まずはNextJS以外をRemixベースのプロジェクトに乗せ、その後NextJSのページをひとつずつRemixに乗せていくことにした
基盤移行 ~Remix, bun~
どうせコストがかかる移植なので、ついでに node + pnpm を bun に移行するのも試してみることにした。
シンボリックリンクのせいで pnpm と docker の相性が悪いのが気になっていたので。
(podman だとマウントの仕組みが違うのでこのへん上手くいくらしいですね。)
dockerでbunのイメージを取得し、適当に docker 設定書いて bun create remix@latest で構築。
特に障害なく、するっと起動してくれた。
ここに元の package.json からnextjs関連を除外し、bun install で追加。
src/components/ など独立した部分を少しずつ移植し、動作確認をしていく。
まずは ladle、と組み始めてすぐにエラーが発生。
bun がダメなのだろうか…と思ったが特に issue 等見つけられず。
vite は bun で動くっぽいので ladle も問題ないはずなのだが。remix vite は動いてるし…。
ん…?vite…?
ということで ladle と remix での vite.config.ts が競合していただけだった。
それぞれ別の vite.config.ts を参照するようにして無事解決。
playwright はさほど苦労せず動いた。
ほぼ Dockerfile を修正しただけ。
vitest も特に問題なし。設定ファイルが vitest.config.ts になってるのはえらい。みんなそうして。
誤爆で起動した bun test がキモいぐらい早かったので一瞬どちらにするか迷ったが、vitestでも十分すぎるぐらい早いので不満はなく、また vitest ui が非常に便利で手放すのは惜しいので、vitest のままにした。
今後 vitest の速度が問題になってきたら再検討しよう。(99.9%ありえないと思うけども)
余談だが、bun test だと bun 組み込みのテストランナーが起動してしまうので bun run test にしないといけない。ちょっとコマンド体系を考え直すべきか、あるいはいつも run を省略している習慣を直すべきか…。
// 結局、上記の問題は makefile にすべて集約させることで bun コマンドを直接叩く機会をほぼゼロにするアプローチになりそう。
周辺ツールがだいたいOKだったので、そろそろ remix 本体に着手していく。
ここで、 bun run dev で remix を起動しているはずがなにやらおかしいことに気付く。
色々試してみた感じ、どうやら remix ではなく bunjs 本体としてのプレーンなサーバーが起動しているようなのだ。
エラーではなく普通にページが表示されていたので、これが bun remix の初期ページなのだと思っていたがそうではなかったらしい。
こんな紛らわしいもんを初期表示にするなァ……
最終的な答えは「bun の公式 Docker コンテナを使っているので Node が入ってない」だった。
互換性の問題があるので、bun で実行しても Node で動かしてくれるらしい。 bun run --bun だと bun で動くが、そうでなければNodeが使われる仕様っぽい。
そういえば Dockerfile いじってるとき playwright が上手く起動しないケースあったけど Node の有無か。謎が解けたねえ。
コンテナ構築を修正。
- oven/bun から node:slim に変える
- Dockerfile 内で npm install -g bun
としたら見慣れた remix の初期画面が開いた。勝利。
ここでかなりの時間を溶かしてしまった。
我ながらよくこんなん気づけたな。えらい。
「結局 Node で動くなら bun いらないんじゃ?」という考えも浮かんできたが、pnpm の場合実体がないからか docker run のたびに pnpm install --force しないといけないのがかなりストレスだったので、Dockerと組み合わせる場合はパッケージマネージャーのみでも bun の恩恵は大きいと判断し使用を継続することにした。
むしろ、ランタイムが Node のままである方が互換性問題が絶対起きないので嬉しいまであるかもしれない。
remix-flat-routes を導入してセットアップ。
app/login/page.tsx を routes/login/route.tsx に移植してみたところちゃんと表示出来た。
記載してない細かいところでは "use client" を消して回ったりしてたが、既存のコードに大幅な改修を入れる場面はなく、あまり大きな労力はかけずに移行できた。
時間を食ったのはほぼ bun の挙動まわりなので、 remix 部分の移行コストはほとんどなかったと思う。
細かい動作までは見ていないが、あとは各ページを移植していけばよいだろう。移行成功と見なしてよさそうだ。
NextJS → Remix
pnpm → bun
への移行自体は一旦これでほぼ完了!
remix-flat-routes を導入してセットアップ。
の部分だが、同じくv2 の flat-route に違和感を覚えている人と議論する機会があった。
お互い flat-routes に反対だったのに、結論は flat-routes 使うべきになったのでこれは外すことにした。
要点は以下:
- flat-routes であればURL一覧の機能を果たしてくれる。ドキュメントが無くてもすぐわかる
- routes/ 配下の量が多すぎて困るほどの開発規模にならない
- さらに routes/ 配下は基本的に実装本体を置かない薄いレイヤーになるので、普段触る領域ではなく、そもそも大量に並んでいても問題になることがない
一方でやりたくない意見としては「URLが多くなると将来的にヤバいような気がする」「なんか気持ち悪い」などお気持ち部分しかなく、flat-routes への具体的な否定意見が出せなかったのでじゃあ採用してみようという流れ。
慣れれば気にならないのでは?ということでここは一旦 remix の流儀に則ってみるべきか。
bun.lockb
どうせ手で調整する機会無いから差分見えないバイナリでもいいよね、と思っていたが dependabot が機能しないらしい。考えてみたらそりゃそうだ。
yarn.lock を吐き出す機能があるらしい。以下にだいたいまとまっていた。ありがたい。
dependabot から PR 作成されたら bun.lockb の方も更新して同期取るアクション案が issue にあった。まだ実際に確認してないが真似してみよう。
lockb について色々調べた結果、pnpm に戻すことにするかもしれない。
理由はバイナリをgitに入れた場合に差分でファイルサイズが膨れ上がってしまうのを懸念したのと、dependabotに拾わせるためにあれこれ構築するのが結構大変そうなため。
yarn.lock で管理するというのもだいぶトリッキーなので嫌な感じはある。
ファイルサイズについては今更ながら git lfs の存在を知ったので、これとの兼ね合いも見てどうするか決めていきたい。
どうせ nodejs で動かすなら、あれこれ考える必要の少ない pnpm でいいような気もしてきた。インストール時間も十分すぎるほど早いし。
結局 stlatica で使うのは無難な pnpm にした。
中身見れないデメリット、git管理しにくいデメリットが無視できないという判断。
移植時エラー
特定のページ移植直後に、関係ないページ含めて全部吹き飛ぶ現象が発生。
react-easy-crop を使っている画面で、canvas 等を内部で使っていてサーバーサイドで落ちているように見える。
// 最初からあると落ちるのに、ホットリロードで途中からねじ込むと問題なく表示できるという現象が起きたりしている。その状態でF5押すと当然のように落ちる。
SSRうんぬんではなく、どうやら react-easy-crop がダメっぽい?
react-cropper で作り直したところ問題が起きなくなった。
元のプログラムが良くない可能性もある。(私が書いたものではないので完全に把握しきれていない)
具体的な原因ははっきりしていない部分があるが、react-cropper でも特に問題なさそうなのでこれでいくか。
移植完了したらスクラップの内容をまとめて記事にしよう、とか考えてたけどほとんど移植する中身なかったので記事にまとめないと思います
bunで彷徨って結局やめたのが内容の大半になっている