技術的負債はいつ解消すべきか? – HERP が React 移行で使った3つの判断軸

HERP の主力プロダクト HERP Hire は、2025 年で開発開始から 9 年目を迎える。
長く運用されるプロダクトとなってきたので、事業の前提やユーザーの期待が変化する中で技術的負債とどう向き合うかが重要な課題となっている。

技術的負債の解消について、多くの記事やドキュメントでは「どうやって解消するか」というアプローチに焦点が当てられているように思う。
しかし、「どうやって解消するか」以上に、「いつ解消するか」 というタイミングの判断が、その後の成否を左右することが多いと考えている。

この記事では、2018 年から jQuery + Pug で構築・運用してきた求人ページ機能を今年になって React に移行している事例をもとに、HERP がどのように技術的負債に向き合い、どのタイミングで解消すべきと判断したのか をまとめる。

単なる技術スタックの移行事例ではなく、「いつ負債を返すのか」という意思決定の事例として紹介したい。

技術的負債の定義

技術的負債の解消について語る前に、まずこの記事における「技術的負債」の定義を明確にしておきたい。

技術的負債とは、「開発と共に得られていく知識や理解と目の前のシステムとの乖離が引き起こす生産性低下」のことである。
つまり、コードが古いから負債なのではなく、知識や理解を得てシステムの前提がズレていくからこそ必然的に負債となるのである。

技術的負債解消の本質的な難しさ

この定義を踏まえると、技術的負債の解消タイミングが難しい理由が見えてくる。
「機能開発とのトレードオフがあるから難しい」というのはもちろんあるが、実はそれ以上に本質的な難しさがあると考えている。

例を挙げて考えてみよう。

  1. 事業が「今後は A の方向に進む」と決定した
  2. 既存システムは A’(A とはズレた状態)になっている
  3. A に向けて技術的負債の解消を進める判断をした
  4. しかし半年後、 事業方針が B に変わった

このとき、「A に最適化するための投資が、B に対する新たな負債になる」という皮肉な事態が発生する。

HERP でもこの種の失敗をかなりやってきてかなり痛い目に遭っている。
例えばサービス開始当初は複数の SaaS を提供する前提で事業を進めていたので、複数 SaaS から利用されそうな機能をモノリスになっていたコードベースからマイクロサービスとして切り出して開発したことがある。しかし、その後 SaaS を複数展開するという方向性はしばらく停止することになり、単なる分散モノリスとなって運用性が下がるだけの悪化した事態を招いてしまった。
もっと技術的な事例では、ドメインエラーを上手く扱うために関数型プログラミングのパラダイムを導入したくてコードベース全体を fp-ts 風にしたが、その後 fp-ts が Effect-TS に取って代わられることになったのでコードベース全体が負債と呼べる状態になったこともある。

このような失敗を経てようやく心から理解したが、 負債解消とは「現在の理想」に合わせることではなく、未来のあるべき理想の方向に合わせていく作業 である。
そして、その未来の方向性は容易に変わりうるし、その変化に対応できることが事業の価値に直結する。
方向性の変化は事業の変化だけでなく、組織の変化、市場構造の変化、技術コミュニティの変化、……などと多岐にわたるので一層話が難しくなってしまう。

このことに自覚的でないとよく陥ってしまうのが、「目の前の課題解消のための技術的負債解消」というアンチパターンである。
例えば今求められている新機能の開発のためにこれまでの実装や設計が障壁となっている時に、一旦負債を解消してから開発に取りかかるというのは、生産性向上の観点からすると一見合理的に見える。
しかし先に説明したように、眼前の課題解決のためだけに負債解消を進めると、その後に得た学習結果や前提の変化によって、むしろ将来的な生産性の低下という負債を生むことになりかねない。

こういった 「技術的負債の解消が新たな負債を生む可能性がある」という逆説的な難しさ が、技術的負債を解消すると決めても成功まで至りづらく、進めにくさを生む原因である。

ここまで技術的負債の解消タイミングの難しさについて述べてきたが、では実際にどのようなタイミングで解消すべきなのだろうか。

これまでの失敗と成功の経験から、僕は以下の 3 つの条件が揃ったときが最適なタイミングだと考えている。

  1. 今後の開発量が増える方向性が明確で、投資効果が高いこと
  2. 負債解消で得られる資産が、事業の方向転換があっても価値を失わないこと
  3. 事業と技術で未来の前提が揃っており、投資判断の合意が取れていること

HERP が今になって求人ページ機能の React 移行を決断できたのは、この3つが揃っていたからだった。
その事例を次に紹介し、最後にこれら3つが重要な理由をまとめる。

事例:HERP が求人ページ機能の React 移行を決断できた理由

求人ページ機能の歴史と課題

HERP Hire では 2018 年から求人ページ機能を提供しており、jQuery + Pug で構築していた。

当時の ATS(企業向けの採用管理システム本体)はすでにモダンな Web フロントエンドフレームワークを使っていたため、技術的には React で構築することも可能だった。しかし、当時のレガシーブラウザ対応要件や SSR の実現しやすさなどを考慮して、jQuery + Pug という選択をした。

当初はシンプルな機能だったため jQuery のコードも軽量だったが、その後の機能追加により徐々に複雑化していった。
例えば i18n(多言語化)対応、フォームのカスタマイズ機能、カスタム項目の追加、note pro 連携など、動きのあるものも含めかなり複雑化していた。
特に求人への応募フォーム機能については、巨大な1ファイルにバリデーションやプレビューなどの様々なロジックが記載されており、誰も仕様を把握できない状態になっていた。

機能追加のたびに Pug も jQuery もコードが複雑化し、DX は下がる一方だった。
一方で、求人ページへの機能追加は散発的で改修頻度が高いわけでもなかったため、この解決はずっと後回しになっていた。

事業の方向性の変化

しかし、2024〜2025 年にかけて、状況が大きく変わった。
HERP は長らく「求人企業向けの SaaS を提供する会社」だったが、事業のテーマが大きく変化したのである。
単なる ATS の提供にとどまらず、「SaaS × マッチング」 というビジネスモデルへとシフトし、採用市場でのマッチング体験そのものを改善する事業へと舵を切った。

この方向性により、求職者向けの体験に手を入れる頻度は確実に増えることが見込まれた。
求人ページ機能はその中心であり、改善ニーズの増加がほぼ確定したのである。

React 化を決断できた 3 つの理由

この事業の方向転換を踏まえ、HERP は求人ページ機能の React 化を決断した。
その判断には、先に述べた 3 つの条件が揃っていたことが大きい。

1. 求人ページ機能は、事業成果向上の重要なファクターになる領域だった

事業の方向性変更により、求職者向けに便利な機能開発が今後確実に必要になると予測された。
例えばブランドやカスタマイズ性の拡張、フォーム UX の大幅改善、アクセスログや分析の拡張などなどである。

そんな大きな期待がある中で、現行の jQuery 実装のままでは更に複雑なロジックを実装していくことに限界があるのは明らかだったので、この移行の費用対効果は高いと判断できた。

2. React化で得られる便益は、再ピボットがあっても無駄にならない投資だった

今回の移行で得られる資産は、React 化による機能追加のしやすさだけにとどまらない。
既存の仕様の整理のためにブラウザテストを整備したり、コンポーネント化したものを容易に Storybook で扱えるようになったりなど、最新のプラクティスに則りやすくなる。

また、将来的に Next.js で運用している求職者向けメディア事業部にオーナーシップを委譲する可能性も考慮しているので、ファーストステップとして React に移行するというのも投資として妥当性が高いと考えられた。

これらはすべて、事業の方向性や状況が再度変わったとしても無駄にならない類の投資だった。
むしろ、どの方向に進むにしても開発の基盤として役立つものである。

3. 技術側と事業側などステークホルダー間で前提を揃えられた

最も重要だったのは、技術側のニーズだけで進めたのではなく、事業の未来と技術の現状を同じ前提で議論できたことである。

  • 求職者向けの改善は今後確実に増える
  • 既存構造では今後求められる開発速度が出しづらい
  • React 化は再ピボットしても価値が残る

これらをボードメンバーや開発チームのリーダー、フロントエンドエンジニアなど社内のステークホルダーと共有し、「今こそ投資すべきである」という合意形成ができた。
技術的負債の解消は、この「前提の共有」ができるかどうかで成否が大きく変わるので、ここで理解を得られたのはとても良かった。

3つの理由

技術的なアプローチ(概要のみ)

やると意思決定できたので、あとはやるだけである。
詳細まで踏み込むとこの記事の本題と逸れるのでさらっと紹介すると、概ね以下の通り。

1. Pug + jQuery → React をページ単位で置き換え
Pug に渡していたデータを React の props としてほぼそのまま受け付けるようにし、renderToString で HTML にした上で Pug で記載、