はじめに
初めまして、RevComm の 楽桑 と申します。
MiiTel Call Center (CC) フロントエンドで React 18 → 19 のアップグレードを実施した。単なるバージョンアップではなく、Semantic UI の完全削除と Recoil から Jotai への移行もまとめて片付けた。
この記事では、AI Agent(Claude Code)を効率化ツールとして活用し、このプロセスをどう加速させたかを紹介する。
背景
プロジェクト構成
- Next.js 14 (Pages Router) + Static Export → S3 配信
- UI: Semantic UI (semantic-ui-react) + MiiTel Design System (MDS) + Emotion CSS-in-JS
- 状態管理: Recoil
- リアルタイム: Apollo Client + GraphQL Subscriptions (WebSocket)
React 19 アップグレードの動機
React 19 の新機能が欲しい、というより周辺事情が同じ方向を指していた:
- 依存ライブラリが次々と React 19 を前提にし始めた
- Semantic UI が React 19 未対応かつメンテ停滞。他のアップデートの足枷になっていた
- Recoil は公式アーカイブ済み
- ついでに React 19 本体の改善(型、Actions など)も取り込める
1. React 19 本体より、周辺作業のほうが重かった
1-1. 実際に時間を使ったのは実装以外の仕事だった
React 19 本体の breaking changes 対応は、実はそこまで重くなかった。useRef の引数必須化、RefObject<T | null> の型変更、Symbol → string の明示変換 — いずれも機械的な修正で、数時間で片付く。
実際に時間を使ったのは実装以外の仕事だった:
- 30 以上ある依存ライブラリの React 19 対応状況を一つずつ調べる
- Semantic UI の撤去後に全画面で発生する CSS リグレッションを特定・修正する
- Recoil から Jotai への移行で、60 個以上の atom と多数の hooks 呼び出しを漏れなく置換する
- セッションをまたいで「前回どこまで進んだか」を記録・引き継ぐ
React のメジャーアップグレードは「バージョンを上げる作業」より「上げるために必要な調査と確認」のほうが圧倒的にコストが大きい。
1-2. AI Agent を入れて変わったこと
Claude Code を導入して変わったのは、コードを書く速度もだが、それ以上に判断に入るまでの時間が短くなった。
「このライブラリは React 19 に対応しているか?」「この CSS の差分は直すべきか受け入れるべきか?」— こういった判断の前提となる情報収集・整理・比較を AI に任せることで、人間は「何を採用するか」「その差分を受け入れるか」の意思決定に集中できた。
2. Claude Code に任せたのは「判断以外」
「判断以外」という境界にたどり着いたのは、最初からではない。まず丸投げして失敗し、そこから「何を任せて、何を任せないか」を学んだ。
2-1. 最初の失敗 ― ボタン移行を “丸投げ” したら崩壊した
最初は役割分担を決めず、AI に全部任せてみるところから始めた。
「Semantic UI の Button を全部 MDS の Button に置き換えて。」
Claude Code は長時間かけて大量のファイルを書き換えた。diff はそれっぽく見えたが、ブラウザで開くとボタンのサイズがページごとにバラバラ、アイコンが消えている箇所があり、Primary と Secondary が入れ替わっている箇所まであった。
実際のコミット履歴がその過程を物語っている:
3/5 refactor: Migrate button components to MDS ← 初回の一括移行 3/5 fix: Match reset and edit button styles ← 即座にスタイル崩れ発覚 3/5 fix: correct edit button text color 3/5 fix: match edit button layout and height 3/10 fix: resolve interactive element nesting 3/11 feat: introduce SecondaryButton component ← ラッパーコンポーネントが必要と判明 3/13 feat: enhance button styling (8コミット連続) 3/14 feat: replace SecondaryButton with StyledButton 3/17 feat: replace LinkIconButton with SupportLinkButton 3/17 fix: unify LinkButton primary colors ← 12日後、やっと安定 合計: 42 コミット / 36 ファイル / +968 -677 行
Semantic UI の Button は props が多彩で、MDS とは一対一で対応しない。つまりこの変換は機械的な一括置換ではなく、各使用箇所で “MDS ではどう表現するか” を決める判断の集合だった:
Props マッピング(実際の PR より): label → children ← 機械的 icon → startIcon ← 機械的 isLoading → loading ← 機械的 isFullWidth → fullWidth ← 機械的 color="alert" → variant="negative" + CSS 上書き ← 判断が必要 color="plain" → variant="default" + 白背景上書き ← 判断が必要 color="secondary" → SecondaryButton ラッパー新規作成 ← 判断が必要 isLinkButton → LinkButton / LinkIconButton 新規作成 ← 判断が必要
判断が必要な箇所を丸投げしたので、AI はそれっぽく見える変換を量産しただけになった。
2-2. 学び ― “丸投げ” ではなく “分解して委譲” する
この失敗から、移行作業は 3 ステップに組み替えた:
- 対応が必要なファイルを AI に洗い出させる(この段階では置換しない)
- ファイルごとに対応コストを分類する(機械的置換 / props 読み替え判断 / 代替なし・自前実装)
- コスト大きい方から着手し、人間のレビューを必ず入れる
この流れに変えた瞬間、型崩れはほぼ消えた。AI Agent は “網羅” と “分類” には強いが、判断が混ざった実装を丸ごと投げると表面的な正しさで走ってしまう。判断は人間が先に固めて、AI にはその方針に沿った実装だけを任せる。これが一番効いた基本動作だった。
2-3. 依存ライブラリ調査を AI に任せる
package.json を起点に、全依存ライブラリの React 19 対応状況を横断調査させた。
Claude Code に投げたプロンプト(要約):
package.json を読んで、全依存ライブラリの最新バージョンと React 19 対応状況を調査。対応済み / 未対応 / 要調査 で分類した表を作って。 未対応のものは issue やリリースノートへのリンクも付けて。
返ってきた整理表:
| ライブラリ | 状態 | 備考 |
|---|---|---|
| react-hook-form | ✅ 対応済み | v7.52.0 |
| emotion | ✅ 対応済み | - |
| react-konva | ✅ 対応済み | v19.0.1 |
| Semantic UI | ❌ 未対応 | メンテ停滞 |
| Recoil | ❌ アーカイブ済み | Jotai へ置き換え |
| react-draggable | ⚠️ findDOMNode 依存 | 自前実装必要 |
この表があるだけで意思決定のコストが激減する。人間は「何から片付けるか」を決めるだけでよくなった。
2-4. Recoil → Jotai の置き換えを AI と分担する
Recoil は Meta が公式にアーカイブ済みで、React 19 の concurrent features との相性も怪しくなっていた。このタイミングで Jotai に乗り換えた。
両者とも「atom 単位で状態を管理する」思想は同じなので、API のマッピングは素直。ただし atom が 60 個以上、useRecoilState / useRecoilValue / useSetRecoilState の呼び出しはそれ以上ある。機械的に置換できる部分を最大化しつつ、微妙に違う箇所だけ個別判断、という進め方にした。
// Before: Recoil import { atom, useRecoilState } from 'recoil'; const selectedAgentAtom = atom<Agent | null>({ key: 'selectedAgent', // 一意な key が必須 default: null, }); const [agent, setAgent] = useRecoilState(selectedAgentAtom); // After: Jotai import { atom, useAtom } from 'jotai'; const selectedAgentAtom = atom<Agent | null>(null); // key 不要 const [agent, setAgent] = useAtom(selectedAgentAtom);
移行の進め方:
grepでatom({/selector({/useRecoilState/useRecoilValue/useSetRecoilStateを全検出- ファイル単位で Claude Code に変換を指示、diff をレビュー
- 型チェック & テスト → 次のファイルへ
この規模の置換はまさに「網羅性が必要な機械的作業」で、AI との分担が効く典型例。人間がやると「このファイルは変えたっけ、こっちはまだだ」となりがちな作業を、AI が一切こぼさずに進めてくれる。
2-5. Storybook MCP でコンポーネントのバリエーションを網羅する
Semantic UI → MDS の移行では、「Semantic UI の Dropdown を MDS では何に置き換えるのか」「どんな props があるのか」「どの Story で確認できるのか」を調べる段階で時間を取られる。
Storybook MCP(@storybook/mcp / @storybook/addon-mcp)は、この「コンポーネントを知る」段階を AI Agent に任せるツール。Storybook 上のコンポーネント一覧、各コンポーネントの API・props・Story 情報を AI に提供する。
人間: 「Dropdown を MDS に移行して」 AI Agent: 1. Storybook MCP で Dropdown のドキュメント・props・Story 一覧を取得 → MDS Select / Menu が候補、Single / Multi / Searchable のバリエーションがあると把握 2. Storybook MCP で各 Story のプレビュー URL を取得 3. chrome-devtools MCP でその URL を開き、getComputedStyle でベースライン取得 4. コードを置き換え 5. 再度 chrome-devtools MCP で計測し、差分を比較
Storybook MCP が「何があるか、どこで見られるか」を提供し、chrome-devtools MCP が「実際に開いて計測する」。この 2 つの MCP の組み合わせで、AI Agent がコンポーネントのドキュメント読みからビジュアル検証までを一貫して行える。
2-6. 進捗記録を AI に任せる
作業セッションが終わるたびに、Claude Code に進捗サマリを Notion に書かせる運用にしている。
以下は実際に Notion に蓄積された記録の抜粋。Semantic UI 移行状況は、セッションごとに自動更新される:
Semantic UI → MDS 移行状況(4/10 更新) ✅ 完了: MDS 代替あり(16ファイル) Popup系(6ファイル)→ MDS Tooltip / Popup Tab系(4ファイル)→ MDS Tabs Dropdown系(6ファイル)→ MDS Select / Menu Input系(2ファイル)→ MDS Input ✅ 完了: MDS 代替なし(4ファイル) List(2ファイル)→ HTML ul/li Statistic(1ファイル)→ HTML div + Emotion CSS Transition(1ファイル)→ CSS animation + useFadeAnimation hook ✅ semantic-ui-react / fomantic-ui-css パッケージ削除済み
MDS canary テストの結果もイテレーションごとに記録される:
| イテレーション | 変更内容 | 結果 |
|---|---|---|
| 1 回目 | peer deps + testing-library 更新 | ✅ 型チェックパス / ✖ 1 test suite 失敗(React 内部 API に依存したビルド構成が React 19 でエラー) |
| 2 回目 | styled-components 6.4.0 | ✖ 同じエラー継続 |
| 3 回目 | vite.config external に react/jsx-runtime 追加 |
✅ 117/117 test suites 全パス |
こういう表が作業の副産物として Notion に残る。後から見返す・チームに共有する・引き継ぐ、のどれも低コストでできる。手動で議事録を書く必要がなく、作業しているだけでドキュメントが蓄積される。
この蓄積は人間が確認するときの根拠になるだけでなく、AI Agent にとっても重要なコンテキストになる。次のセッションで Claude Code が前回の記録を読み込むことで、「前回どこまで進んだか」「どのアプローチがうまくいった / いかなかったか」を踏まえて作業を再開できる。人間が「これどうだったっけ」と振り返る根拠であり、AI が次の一手を打つための判断材料でもある。
3. 視覚リグレッションを AI と一緒に潰す
3-1. 一番つらかったのは、テストでは拾えない崩れだった


Semantic UI を剥がすと、コード上は何も壊れていないのに全画面の見た目が微妙に崩れる。fomantic-ui-css (Fomantic UI は Semantic UI のコミュニティフォークで、CSS テーマ部分を提供するパッケージ) がグローバルに撒いていた reset CSS、Lato フォント、line-height などが一斉に消えるため。
pnpm ts → PASS、pnpm build → PASS、pnpm e2e:run → 40/40 passed。しかしブラウザで開くと、チェックボックスのサイズが違う、日付入力の幅がずれている、行間が微妙に変わっている。ビルド成功 ≠ UI 正常。
3-2. 目視の根性論ではなく、差分の観測に寄せた
Chrome-devtools MCP(MCP = Model Context Protocol。AI Agent が外部ツールを操作するための標準プロトコル)を使い、main と PR で同じ要素の getComputedStyle を自動取得して比較する方式にした。「何が違うか」を先に機械的に洗い出してから、人間が判断する流れ。
3-3. reset CSS 欠落で input margin が復活した
Semantic UI はコンポーネントライブラリであると同時に、fomantic-ui-css(Fomantic UI は Semantic UI のコミュニティフォークで、CSS テーマ部分を提供するパッケージ)というグローバル CSS もバンドルしていた。この CSS には normalize / reset ルール(input { margin: 0 }、body { overflow-x: hidden } など)が含まれており、アプリ全体が暗黙的に依存していた。Semantic UI のコンポーネントを全て MDS に置き換えた後、fomantic-ui-css ごと削除したことで、これらのリセットルールが消失した。
上記二つの画像の場合, chrome-devtools MCP 経由で getComputedStyle を main / PR で比較。
実際のアウトプット:
// main (/callcenter/report/) - label 内部構造 {"tag":"LABEL", "rect":{"w":30,"h":30}, "margin":"0px"} {"tag":"INPUT", "rect":{"w":30,"h":30}, "margin":"0px", "border":"1px solid rgb(23, 100, 233)"} {"tag":"SPAN", "rect":{"w":14,"h":21}, "text":"日"} // PR (/callcenter_preview_4868/report/) - 同じ label {"tag":"LABEL", "rect":{"w":37,"h":36}, "margin":"0px"} {"tag":"INPUT", "rect":{"w":30,"h":30}, "margin":"3px 3px 3px 4px", "border":"1px solid rgb(23, 100, 233)"} {"tag":"SPAN", "rect":{"w":14,"h":21}, "text":"日"}
INPUT の margin が 0px vs 3px 3px 3px 4px。fomantic が提供していた input { margin: 0 } が消え、ブラウザデフォルトの margin が復活していた。label のサイズが 30×30 → 37×36 に膨張。reset.css に追加して解決。
3-4. 大事だったのは「直す差分」と「受け入れる差分」を分けること
Semantic UI と MDS は別物なので、MDS に寄せた時点で見た目が変わる箇所は必ずある。検出された差分のうち、直さない差分のほうが件数は多い。
| 直すべき | 受け入れるべき |
|---|---|
| 機能の破壊(クリック領域、要素の重なり) | デザイントークン由来の変化(色・余白・書体・ラディウス) |
| 意図しない結果(リセット CSS 欠落、フォールバック) | MDS 側の改善(focus ring、ARIA 属性) |
| ユーザーが混乱するレイアウト崩れ | UX の改善 |
ピクセル一致ではなく、「意図しない破壊がないこと」 を目的にした。
3-5. ここでの AI と人間の境界
AI は差分検出と原因特定の補助が得意。一方、その差分が仕様か不具合かを判断するのは人間の仕事。この分担が、視覚リグレッション対応では特に効いた。AI が差分をピクセル単位で絞り込み、人間がその箇所を重点的に目視確認する。
4. 手順と判断基準を Skill にする
アップグレード作業の佳境に入った頃、社内の別チームが Claude Code の Skill を使って移行作業を標準化していることを知った。そのアプローチを参考に、今回の Semantic UI → MDS 移行にも Skill を導入した。
4-1. 単発で使うだけでは、再現性が出ない
コンポーネント移行は似た作業の繰り返しになる。そのたびにプロンプトを考えるのは非効率だし、個人技のままだと品質もぶれやすい。
4-2. Semantic UI → MDS 移行の流れを Skill 化した
Claude Code の Skill(再利用可能なカスタムコマンド) として、移行ワークフローを標準化した。
実行すると AI Agent が以下を自動で進める:
- 対象コンポーネントの使用箇所と Storybook を網羅的に検索
- chrome-devtools MCP でベースラインの
getComputedStyleとスクリーンショットを取得 - MDS の対応コンポーネントに置き換え
- 移行後の
getComputedStyleとスクリーンショットを取得し、ベースラインと比較 - 差分を分類して対応
4-3. Skill に埋め込んだのは、手順だけではない
差分を 3 層に分類する判断基準まで含めて標準化した:
- 自動修正層: 意図しない破壊(リセット CSS 欠落など)→ AI が修正
- 人間判断層: デザイントークン由来かもしれない差分 → レポートして人間に判断を戻す
- 記録のみ層: MDS 仕様として正しい差分 → ログに残す
手順だけでなく判断基準を埋め込むことで、別メンバーが別コンポーネントを移行するときも同じ品質で作業できる。この Skill は社内マーケットプレイスに登録しており、チーム内の誰でも同じコマンドで移行作業が回せる。
4-4. AI 活用を「うまく使える人」依存にしない
AI を使うこと自体ではなく、AI が機能する作業設計のほうが重要だった。再利用可能な形にしておくことで、プロンプトの書き方や AI への指示の仕方を個人に依存させない。
5. まとめ
5-1. AI Agent が速くしたのは、実装そのものより「判断に入るまで」
調査、整理、比較、記録のコストを大きく下げられた。React 19 本体の breaking changes 対応よりも、依存ライブラリ調査・CSS リグレッション検証・レビュー対応の効率化のほうが AI Agent の貢献が大きかった。
5-2. 実務で AI Agent を使うなら、まず任せる範囲を決める
- AI に任せる: 網羅・比較・記録のような、正確さと量が求められる作業
- 人間が担う: 意図と責任が伴う判断
- この境界を先に決めると、使い方がぶれにくい
5-3. 今回の学び
- AI は万能ではないが、作業の前後にある重い仕事にはかなり効く
- 特に、大規模移行のような「調査と確認が多い仕事」と相性がよかった
- 単発活用より、手順と判断基準を仕組みに落とすほうが持続的に効く
※ 今回の Skill はアップグレード作業の佳境に入った頃に導入したため、恩恵は限定的だった。最初からあれば、繰り返し作業の品質と速度をもっと引き上げられたはずだ。
使用ツール: Claude Code / chrome-devtools MCP / GitHub CLI / Notion MCP