この記事はNext.js版の内容です。現在はAstroで構築し直したため、情報が古い可能性があります。
当時のリポジトリは こちら にあるので参考にしてください。
このブログを公開してから約1年が経過した。
それに伴い、リファクタリングを実施した。
UIはほとんど変更していないものの、各処理を最適化したことでUXの向上が期待される(要検証)。
さらに、内部で使用するライブラリを見直すことでビルド時間を短縮し、開発体験の改善も図った。
v1は参照用として別のブランチに保存している
今回のリファクタリングにおいて意識した点は以下の4つである。
- ライブラリのバージョンを最新版にする
- 開発体験を向上させる
- 必要なライブラリのみ使う
- 最適な実装を行う
ライブラリのバージョン管理には Renovate を使用している。
アップグレード対象のライブラリが見つかれば、自動でPRを作成し、PRが作成されると同時にCIが起動。
ビルドスクリプトや静的コード解析を実行し、CIが通れば即マージという運用だ。
しかしながら、メジャーアップデートが絡むと、当然のごとくCIがエラーを吐く。
CIが通らない場合はローカル環境でコードをpullして修正する必要があるのだが、その手間を後回しにするのが人間のサガというもの。
結果、気づけばアップグレード待ちのライブラリが10個以上たむろするという、面倒くさい状況が生まれてしまった。
ライブラリのアップグレードを放置するのはセキュリティ的によろしくないし、新機能を享受できないのも何とも言えず心理的にもやもやする。
というわけで、時間をとって、全ライブラリを最新バージョンにアップグレードした。
作業自体は概ねスムーズだったのだが、一筋縄ではいかなかったのが Contentlayer というライブラリだ。
MDXファイルを型安全なJSオブジェクトへ変換する便利ツールであるが、残念なことに資金不足でメンテナンスがストップしてしまっていた。
この問題には少々頭を悩ませたが、最終的には Contentlayer2 というフォーク版を採用することにした。
Contentlayer2は新機能の追加が期待できないものの、必要最低限の機能は揃っているため実用上の問題はなさそうだ。
必要なものを手に入れるだけならこれで十分。
今後の安定稼働を願いつつ、新しい環境での運用を進めていく。
この1年の間に、JavaScript界隈のツールチェーンは激動の時代を迎えつつある。
その波に乗り、より快適な開発環境を求めて以下のツールを切り替えた。
| 用途 | 移行前 | 移行後 |
|---|
| コードフォーマッタ | Prettier | Biome |
| リンタ | ESLint | Biome |
| パッケージマネージャ | pnpm | Bun |
| ランタイム | Node.js | (Bun) |
これにより、4つのツールが2つに集約された。
使うツールは少ないほど管理も楽になる。
Biomeに切り替えた最大の理由は、その圧倒的な実行速度である。
例えば、以前はPrettierとESLintを組み合わせてコードの整形とLintを実行していたが、処理時間はそれぞれ3.9秒 + 10.3秒 = 合計14.2秒 を要していた。
一方で、Biomeはその両方の機能を兼ね備えており、処理時間はわずか0.045秒。その差はなんと約315倍である。
まさに「瞬きする間に終わる」というレベルだ。
しかし、Biomeには課題もある。
例えば、言語サポートが限定的である1ほか、ESLintの豊富なプラグインが利用できないといった制約がある。
ただし、筆者の用途では特に問題なく、必要十分な機能を発揮している。
他に特筆すべきは、追加のプラグインをインストールしたり、煩雑な設定ファイルを書く必要がない点だ。
実際の設定ファイルはこれだけで済む ↓
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
シンプルでありながら強力、そしてとにかく速い。
Biomeは、コード規約を厳格に管理する必要がない環境において、非常に有用な選択肢といえる。
次にBunだが、こちらもBiomeに負けず劣らずの速度を誇る。
まず、パッケージマネージャとしての性能を見てみる。
node_modulesを削除した状態でライブラリを再インストールする場合、pnpmでは23.1秒を要した。
これでもnpmやYarnより速いが、Bunで実行すると5.6秒で完了した。
その差は約4倍。
ここまで速いと、インストール待ちの時間がほぼ気にならない。
また、Bunはランタイムとしても使用可能だ。
公式ドキュメントによれば、Next.jsのローカル開発環境でBunをランタイムとして使うこともできるとある2。
しかし、実際に試してみたところ、[custom formatter threw an exception]というエラーが表示され、不安要素が残ったため、結局ランタイムとしてはNode.jsを使用している。
デプロイに関しては、VercelがBunをパッケージマネージャとしてサポートしているため、問題なく動作している。
ランタイムとしての完全な移行は今後のアップデートに期待したいところだが、現時点ではパッケージマネージャとして活用するだけでも十分に恩恵を受けられる。
リファクタリングの過程で、「本当にこのライブラリは必要か?」と自問自答した結果、いくつかのライブラリを廃止する決断を下した。
その主な対象が以下の2つである。
tRPCは「型安全なAPI通信」を実現できる強力なツールである。
しかし、導入にあたり多くの設定が必要で、その初期ハードルの高さが以前から気になっていた。
また、v11の安定版がいつまで経ってもリリースされないという状況も不安要素の一つだった。
現時点でのtRPCは、長期的に採用するには少し懸念の残る選択肢だったといえる。
一方、このブログはNext.js v15上で動作しており、Server Actionsを活用することで、型安全にAPIリクエストを送信できるようになった。
tRPCを導入した背景には、純粋に「技術的な興味」という動機があった。
しかし、リファクタリングを進める中で、「tRPCを使い続ける理由」が薄れていったことを感じた。
もちろん、Server Actionsを採用したことでNext.jsにロックインされるリスクは意識しておく必要がある。
しかし、それを差し引いても、今のブログの規模ではtRPCを維持するコストに見合うメリットは感じられなかった。
次にMotion(旧Framer Motion)も剥がすことにした。
Motionは、React環境でのアニメーション実装を簡単かつ直感的に行える素晴らしいツールだ。
軽微なモーションから高度なインタラクションまで幅広く対応できるため、一時はこのブログでも多用していた
だが、クライアントバンドルの肥大化の問題やブログという特性上、「魅せるアニメーション」よりも「軽快な読み込み体験」の方が重要だと考え直した。
必要なアニメーションはCSSのtransitionやkeyframesで実装することにした。
この変更により、クライアントサイドのコード量が削減され、バンドルサイズが小さくなった。
結果として、ページの初回読み込み速度も向上し、全体的なパフォーマンス改善に寄与した。
お問い合わせフォームの実装を見直し、これまで使っていた React Hook Form から、Conform に切り替えることにした。
この変更の背景には、Server Actionsへの対応状況が大きく影響している。
React Hook Formは便利だが、現時点ではServer Actionsと組み合わせるにはまだ課題が残っている。
一方、ConformはすでにServer Actionsに対応しており、移行後の実装は驚くほどスムーズだった。
また、メール送信やGoogleスプレッドシートへの書き込みといった処理も、すべて1つのServer Actionに集約したことで、管理の手間が大幅に軽減されている。
フォームの見直しと並行して、JavaScriptのバンドルサイズ削減にも注力した。
クライアントサイドでの負荷を減らすため、可能な限りServer Componentsを利用するように実装を変更した。
この結果、初回描画速度の向上と、クライアントサイドのJavaScriptコード削減によるパフォーマンスの改善を達成した。
ただし、すべてをServer Componentsに移行することが最適とは限らない。
RSC Payloadが増えることで、かえってパフォーマンスが悪化する場合もあるため、試しながら進めている。
最後に、過去のハックな実装方法を一掃し、ベストプラクティスに従うことを目標にコードベースを根本から見直した(ここで書くべきことではないのかもしれないが)。
動作優先で無理やり書かれたコードや、なぜ動いているのか理解が難しい箇所を改善した。
リファクタリングとは、過去の自分と対峙する作業だ。
そのコードがなぜ生まれ、どのように育ち、どれだけの時間を共にしたのか。
そして、なぜそれを手放さなければならなかったのか──その答えを見つける旅でもある。
リファクタリングに終わりはない。
リリースした瞬間から、そのソースコードはレガシーコードとなってしまうことを忘れずに開発していきたい。