v2.0.0 リリースノート: リファクタリング
このブログを公開してから約 1 年が経過した。 それに伴い、リファクタリングを実施した。
UI はほとんど変更していないものの、各処理を最適化したことで UX の向上が期待される(要検証)。 さらに、内部で使用するライブラリを見直すことでビルド時間を短縮し、開発体験の改善も図った。
https://github.com/kkhys/me/releases/tag/v2.0.0
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 の豊富なプラグインが利用できないといった制約がある。
ただし、筆者の用途では特に問題なく、必要十分な機能を発揮している。
他に特筆すべきは、追加のプラグインをインストールしたり、煩雑な設定ファイルを書く必要がない点だ。
実際の設定ファイルはこれだけで済む ↓
biome.json
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space"
}
}
シンプルでありながら強力、そしてとにかく速い。 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 が増えることで、かえってパフォーマンスが悪化する場合もあるため、試しながら進めている。
最後に、過去のハックな実装方法を一掃し、ベストプラクティスに従うことを目標にコードベースを根本から見直した(ここで書くべきことではないのかもしれないが)。 動作優先で無理やり書かれたコードや、なぜ動いているのか理解が難しい箇所を改善した。
さいごに
リファクタリングとは、過去の自分と対峙する作業だ。
そのコードがなぜ生まれ、どのように育ち、どれだけの時間を共にしたのか。 そして、なぜそれを手放さなければならなかったのか──その答えを見つける旅でもある。
リファクタリングに終わりはない。
リリースした瞬間から、そのソースコードはレガシーコードとなってしまうことを忘れずに開発していきたい。