このページの本文へ

FIXER Tech Blog - Development

Vue.jsからReactへの移行で分かった「リアル」 ―どうすればよかったのか?

2025年12月17日 15時00分更新

文● 江藤皓史/FIXER

  • この記事をはてなブックマークに追加
  • 本文印刷

 本記事はFIXERが提供する「cloud.config Tech Blog」に掲載された「Vue.js から React への移行を考えているあなたに」を再編集したものです。

はじめに

 私はこれまで、主にVue.jsやNuxt.jsを使ってアプリ開発を行ってきました。

 そんななか、あるプロジェクトでNext.js(React)への移行を行うことになりました。

 VueもReactもどちらも「コンポーネントベースのフレームワーク」なので、そこまで苦労しないだろうと正直、軽く考えていました。

……が、実際に移行を進めていくと、世界観そのものが違うことを思い知らされました。

 この記事は、そのときに感じた「移行して初めて分かったリアル」 を、できるだけ誠実にまとめたものです。

 移行前に知っていれば避けられた苦労や、逆に React/Next.js に移って初めて分かった「心地よさ」も含めて共有したいと思います。

※本記事は、Next.js の App Router へ移行した際の体験談を中心に書いています。

対象読者

 この記事は次のような人に向けて書いています。

個人開発者・現場エンジニア
・Vue.js/Nuxt.jsを中心に開発してきた人
・Reactが気になっている人
Next.jsへの移行を検討している人
・「Vueの知識って、どこまでReactに通用するの?」と不安に感じている人

チーム・プロジェクトの技術選定担当者
・Vue.js/React のどちらを選ぶべきか判断したい
・エコシステムの違いを実務目線で 把握したい
・「移行コスト」がどのくらいかかるのか、ざっくりイメージしたい

背景

 私はあるプロジェクトにアサインされました。

 そこではすでに Nuxt.js(Vuetify、Pinia等のパッケージ)を使ってアプリが動いており、 ちょうど新サービスの追加・大幅な改善を検討しているタイミングでした。

 これに合わせて、一度技術選定を見直そうという話が出たのが、移行プロジェクトの第一歩でした。

デジタル庁のデザインシステムを使うことに

 新しく進める機能開発では、次のような方針が掲げられました。

・デジタル庁のデザインシステムに準拠することデザインシステム|デジタル庁
・アクセシビリティ(A11y)を満たすコンポーネントを標準化すること

 調査を進めていくなかで分かったのが、

デジタル庁のデザインシステムの コードスニペットがReactで公開されている
digital-go-jp/design-system-example-components-react: デジタル庁デザインシステムのサンプルコンポーネント(React版)

という事実でした。

 つまり、React版のコンポーネントをそのまま活用できれば、

・再実装コストの削減
・品質・アクセシビリティ水準の担保
・継続的なデザイン統一

といったメリットが得られます。

 Vue.js/Nuxt.jsで同じものをゼロから作るよりも、Reactならデジタル庁が提供しているサンプルを使用できるのが非常に魅力的でした。

生成AIを活用した開発

 今回のプロジェクトでは、開発効率を高めるために、エージェント型コーディングツールである「Claude Code」を積極活用する方針も含まれていました。

Claude Code | Claude

 実際にAIにReactのコードを生成させてみると、Vueと比べて次のような違いを感じました。

・生成されるコード量が多い(サンプルがリッチ)
・「とりあえず動く」コードが出てくることが多い
・周辺ライブラリまで理解している
・サンプルコードやデバッグパターンの提案精度が高い

 これは一言でいうと、「AIがReactの世界観に最適化されている」という印象でした。

 Vue.jsも、もちろん悪くはないのですが、

・Vue 2 → Vue 3のような 「文法・書き方・思想そのものが変わるようなレベルの大規模変更」があったこと
・AIが古いVue 2の情報を参照していると、Vue 3のアプリでは動かないコードが出てくること

などの影響で、「そのままでは動かないコード」が混ざるケースも少なくありませんでした。

 また、検索数という観点でReactとVue.jsを比較してみると、世界でも、日本でもReactのほうが常に多いことがわかります。

React, Vue - 調べる - Google トレンド

 検索されているということは、

・エンジニアが採用を検討したり
・トラブルシュートのために調べたり

といったタイミングで React がより多く参照されており、Reactの資料・コミュニティが大きいため、コード生成の事例が豊富であること推測できます

エコシステムと対応ライブラリの数

 React のエコシステムはとにかく巨大です。

 たとえば、

・UIライブラリ
・状態管理
・フォームライブラリ
・SSR/SSG フレームワーク
・アニメーション
・A11y ツール
・テストツール
・AI/LLM 関連ツール

 など、ほぼすべてのカテゴリでReact系ライブラリのほうが量も選択肢も多いという特徴があります。

 Vueはどちらかというと統合的・まとまりが良いエコシステムで、 そのぶん特定領域を深くカバーしてくれる強みがあります。

 ただしAIが理解する前提のデータ量という観点だと、Reactのほうが圧倒的に有利だと感じました。

総合的に判断して React(Next.js)を採用

 こうした背景を踏まえて、次期機能追加ではReact(Next.js)を採用する理由がかなり明確になりました。

・デジタル庁デザインシステムがReact前提で提供されている
・A11y(アクセシビリティ)対応済みの公式コンポーネントをそのまま活用できる
・AIエージェント(Claude Code)を用いた開発では、Reactのほうが生成精度・開発効率が高い
・Vue/Nuxtで同じ品質と速度を再現しようとすると、再実装コストが大きくなる

 これらを踏まえると、

部分的に React を導入するだけでなく、アプリ全体を React(Next.js)へ統一した方が合理的では?

という結論に至りました。

 こうして、最終的にVue.js/Nuxt.jsからReact/Next.jsへ移行することが決定しました。

実際に移行してつまづいたこと

JSX 構文への戸惑い

 まず最初にぶつかった壁は、やはりJSX構文でした。

 ReactではJavaScript内でUIを記述するJSXを使ってコンポーネントを作ります。

 これに慣れるまで、かなり頭の切り替えが必要でした。

 「ボタンをクリックしたらログが出る」程度のシンプルなコードで比較してみます。
Vue.js

<template>
  <button @click="sayHello">クリック</button>
</template>

<script setup>
  const sayHello = () => {
    console.log('Hello World') 
} 
</script>

 Vue.js(Composition API)では、HTMLの延長のような書き方ができます。

・ロジックは <script>
・表示は <template>

と明確に分離されているので、学習コストが低いとよく言われます。

React

export default function App() {
  const sayHello = () => {
    console.log('Hello World') 
  }

  return (
    <button onClick={sayHello}>クリック</button>
  )
}

 React(JSX)では、JavaScriptの関数としてコードを書き、その戻り値としてUI要素を返す形になります。

 ロジックと表示が同じ場所に存在しているのが、Vueとの大きな違いです。

JSX のメリットが腑に落ちるまで
 
 正直なところ、移行したての頃はJSXのコードを見ても、

「どこがUIで、どこがロジックで、どこがイベントで、どこが状態管理なのか」

 その境界が曖昧で、かなり混沌としたものに見えていました。

 しかし、実務でReact/Next.jsを使い続けていくと、次のようなメリットを実感するようになりました。

・自由度が高い

 Vue.jsには、フレームワーク独自の構文が多くあります。

・v-if
・v-show
・v-for
・v-slot
・v-model

など、これらはとても分かりやすい反面、「Vueのやり方」に沿う必要があるとも言えます。

 一方、ReactではUIにそのままJavaScriptを使えるのが特徴です。

{items
  .filter((x) => x.done)
  .map((x) => (
    <Task key={x.id} {...x} />
))}


 このようなJSの関数(filter/mapなど)を使ったロジックを、そのままUIと同じ場所に書けるのは、慣れてくるとかなり快適です。

責務が明確になりやすい

 Vue.jsはtemplate/script/styleとファイル内で役割が分かれていて見やすい一方、画面が複雑になってくると、

・UI
・状態
・計算プロパティ
・イベント
・メソッド

がファイルの離れた場所に散らばりがちです。

 「この処理どうなってるんだっけ?」と追いかけるたびに、画面を上下にスクロールしまくった経験がある人も多いと思います。

 Reactでは、関数コンポーネント(UI を返す関数)の中に必要な情報が集約されるため、

・このstateはこのUIでしか使わない
・この関数はこのコンポーネントの中だけで完結している
・この分岐はこのレンダリングにしか影響しない

といった関係性が、物理的に近い場所にまとまる傾向があります。

 Vue脳のままでは最初はピンと来ないのですが、数週間ほどガッツリReactに触れて「体験として」理解してくると、「なるほど、こういう思想で作られた書き方なのか」と腑に落ちてきました。

(これが分かってくるのは、ある程度数をこなしてからなんですが……笑)

Server Component と Client Component の衝撃

 Next.js(App Router)で最初にぶつかった大きな壁が、Server Component/Client Componentの概念でした。クライアント・サーバーの概念はわかっていても、これらを理解するのには時間がかかりました。

 Nuxt.jsではコンポーネントはクライアントで動作する前提で開発されます。

 SSR(サーバーサイドレンダリング)したとしても、最終的にはクライアント側でハイドレートされます。

 そのため、onClickやv-modelは基本的にどこに書いてもOKです。

 しかし、この考え方はNext.jsでは通用しません

 Next.js(App Router)では、コンポーネントに役割が割り振られています。

 それが「Server Component」と「Client Component」です。

 飲食店で例えると、

・Server Component:料理を作るキッチン担当
・Client Component:料理をお客さんに渡したり、注文を受けるホール担当

みたいな感じです。

 もっと具体的に言うと、以下の役割を負います。

・Server Component:データ取得、認証、DB操作、cookies、RSCなど
・Client Component:入力、DOM操作、アニメーション、画面操作など

 これらの概念がVue.jsにはなかったので、思想の違いがダイレクトに実感できるつまづきポイントでした。

※Next.jsには、クライアントコンポーネントからサーバー関数を呼び出せるServer Actionという機能もあります。ただし今回は、コンポーネントによって役割が明確に分かれるというNext.jsの基本思想を中心に説明するため、Server Actionについては説明していません。

コード例

 以下のようなコードを書くと、Next.jsではエラーになります。

export default function Page() {
  const handleClick = () => {
    console.log('clicked')
  }

  return <button onClick={handleClick}>クリック</button>
}


※このコンポーネントはServer ComponentなのでonClickが使えません Client Componentにしてください

 これを動かすにはこうします。

"use client" // ☆追加!

export default function Page() {
  const handleClick = () => {
    console.log('clicked')
  } 

  return <button onClick={handleClick}>クリック</button>
}

 はい、一行目に”use client”と記載しただけです。

 これでこのコンポーネントはClient Componentと認識され、エラーがなくなります。

 そして、ここで出てくる発想が、

「これもう全部に”use client”つければよくない?」

画面でonClickを使うたびにエラーになるなら、最初から全部Client Componentにすればいいじゃん!!

 私も最初はこれで書き始めました。

 実際、"use client"をつければ、

・useStateを使える
・useEffectを使える
・onClickもonChangeも使える

と、Vue.jsのコンポーネントに近い感覚で実装ができます。

 そのため、一瞬だけ世界が平和になります(しかしこの考えは間違いでした...「認証とセキュリティ」に続きます)。

ライブラリ互換がなく、UI・ロジックを作り直し

 Server/Client Component の考え方に慣れてきたころ、次に直面したのがライブラリと実装方針の違いによる壁でした。

VueのライブラリはReactでは使えない

…当たり前なんですが、問題はその先です。

 Reactのライブラリは膨大で、「定番」 がVue.jsほど明確ではありません。

■Reactで悩むポイントの例

・UI:MUI / Chakra / shadcn / Radix / Mantine …
・状態管理:Zustand / Redux / Jotai / Recoil …
・フォーム:react-hook-form / Formik
・Fetch:SWR / React Query
・CSS:Tailwind / styled-components / emotion / vanilla-extract

 Vueはある程度絞られているので選択のストレスが少ないのですが、Reactはそうはいきません。

 そして「ライブラリを決めれば終わり」ではありません。

■Nuxtの魔法はNextでは通用しない

 Vue.jsのロジックをReactにそのまま流用してもほぼ確実に動きません

 動くとしてもその実装はベストプラクティスとは言えない場合が多いです。

 先述したとおり、Vue.js/Nuxt.js は基本クライアントで動く前提で、

・フォーム入力
・データの保持
・API取得
・UIの切り替え
・stateの共有

などをひとつのコンポーネントの中でなんとなく書いても動く設計になっています。

 これは良い意味での魔法です。

 しかしNext.js(App Router)は設計思想が真逆です。

 Vue.jsで動いていたロジックをそのままReactに持っていこうとすると、

・Server/Clientコンポーネント分割
・認証ロジックの移管
・データ取得の場所の見直し
・状態管理の責務の再定義
・fetchのキャッシュ挙動の理解
・Server Actions移行 etc...

と、ほぼ別物のアーキテクチャ設計が必要になります。

 Vue.jsの頃はUIとロジックを同じ場所でなんとなく書いても成立したけど、Next.jsでは「これはサーバー」「これはクライアント」と役割をはっきり分けないと壊れます。

 ライブラリの選定だけでなく、ロジックの構造そのものを一度更地にして組み直す必要があります。

 これが想像以上に重くて、正直言うと一番しんどかったポイントでした。

認証とセキュリティ

 全部Client Componentで書き始めてしばらくすると、次にやってくるのが「認証どうする問題」です。

 Nuxt.jsの感覚だとこのように考えがちです。

1. トークンをlocalStorageに入れておく
2. ページ側のonMounted/useAsyncData的なところでAPIを叩いてユーザー情報を取る
3. それをグローバル state(≒ストア) に入れて各画面で参照する

 これをNext.jsでもそのままやろうとすると、

・認証トークンがクライアントにべったり残り続ける
・XSSや盗み見のリスクが上がる
・API叩くたびにフロント側でトークンを付与する必要がある

というセキュリティ的にけっこう危ない構成になってしまいます。

 Next.jsでは「認証回りはサーバーで完結させるべき」という思想があります。

 クッキーでセッションを持って、サーバー側で認証状態を判断する考え方で、

・クッキーでセッションを安全に管理
・認証判定もServer Component側で行う
・cookies() やgetServerSession() はServer側限定

 そのため、全部Client Componentだけで完結しようとすると、Next.jsが提供するサーバー側で安全に認証を扱う仕組みを十分に活かせなくなります。

 ここでようやく理解しました。

「あ、これ全部Clientにすれば解決っていう話じゃないんだ」
「認証とか重要なロジックはServerに寄せないと危ないんだ」


 Nuxt.jsから移行してきた自分にとって、この思想の転換が本当に大きな壁でした。

 Nuxtでやっていた一般的なセキュリティ設計に加えて、Next.jsではこのような思想の違いがあるので注意が必要だと感じました。

そもそもレビューできる人がいない

 移行プロジェクトの初期で、実はかなり深刻だったのが、「React/Next.js のコードをレビューできる人が少ない」 という問題でした。

 元々の技術スタックがVue.js/Nuxt.jsである以上、チーム全体の知識もVue.js側に寄っています。

 そのため、Claude Codeが生成してくるReact/Next.jsのコードに対して、

「これって本当にベストプラクティスなの?」「動くけど、この書き方で問題ないの?」

といった判断ができませんでした。

 Claude CodeはReactのエコシステムに最適化されているため、

・とりあえず動くコード
・それっぽいコード
・見た目は正しそうなコード

を普通に出してきます。そしてそれが正しくないケースも普通にあります。

 しかし、当時の私達にはそれを見抜く知識が不足していました。

■レビューが「座学」から始まった

 結果として起きたのが、このレビューフローです。

1. Claude Codeを使ってPRを作る
2. レビュワーが読む
3. よく分からない
4. React/Next.jsのドキュメントを読む
5. マークアップ、State、RSC、Contextの概念を学ぶ
6. 「たぶんここはこうしたほうがいい?」と推測でレビューする

 つまり、レビューの前に「React/Next.jsの勉強」をする必要が出てきたのです

 Vue.js/Nuxt.jsの経験はある程度役に立ちますが、思想が真逆な部分が多いため、Vue.jsのノリで判断すると危ないという瞬間も多く、ドキュメントや技術サイトに頼りながら進める状態でした。

 この「Reactをレビューできない問題」は、単なる技術的つまずきではなく、チーム全体が新しい技術基盤に移る際に避けて通れない「立ち上がりの混乱」 に近いものでした。

どうすればよかったのか

 ここまで振り返ると、Vue.js/Nuxt.jsからReact/Next.js への移行は、想像以上に前提の違いに振り回されるプロセスだったと感じます。

 ただ、後になって思えば「もっとこうしておけばスムーズだったのでは?」と思うポイントがいくつかあります。

1. React/Next.jsの思想を先に学んでおくべきだった

 「そりゃあそうだろ」という話ですが、チーム全員で座学から始めるべきでした。
重要なのはチーム全員が、という点です。

 VueとReactはコンポーネントフレームワークという点では似ていますが、思想はまったく別物です。

・JSXが前提
・UIとロジックが同じ場所にある
・Server/Clientの明確な分離
・データ取得の責務が根本的に違う

 これらを知らないまま移行を始めると、必ずつまずきます。

 「コードを写し替える移行」ではなく「設計思想を学び直す移行」と最初に理解しておくべきでした。

2. 小さく実験するフェーズをもっと早く作るべきだった

 実際の移行では、既存アプリのコードを触りながらReact/Next.jsを学んでいく形になっていました。

 しかし本当は、もっと早い段階で理解のためだけに触る小さな実験環境を用意しておくべきでした。

 例えば、

・RSCのみを扱うミニアプリ
・Server Actionsの挙動だけを確認するページ
・認証(session/cookie)だけを試す簡易プロジェクト

 こうした 目的特化のサンドボックス環境があるだけで、余計な要素に邪魔されず純粋にNext.jsの特性を理解できます。

 先にこうした「安全に失敗できる環境」を作っておけば、思想の理解 → 手触りの確認 → 具体的な実装という自然なステップを踏めて、移行時の不安はかなり減っていたはずです。

3. ライブラリ選定を「理由付き」で行うべきだった

 Reactのライブラリ選定は想像以上に難しく、Vueのように定番が分かりやすい世界ではありません。

 選ぶときの基準を最初に明確にしておけばよかったと感じます。

・公式ドキュメントの充実度
・メンテ状況
・Reactの今の潮流(RSC/Server Actions)との相性
・学習コスト

 「とりあえず有名だから」ではなく、「なぜこれを使うのか?」の理由を言語化して選ぶべきでした

4. レビューできる体制を整える

 Claude Codeはとても優秀ですが、

・正しいけど冗長なコード
・動くけどベストではないコード
・一見正しく見えるけど思想に沿っていないコード

が普通に出てきます。

 これを見抜ける人が少なかったため、レビューが勉強から始まってしまっていました。

(これは「1. React/Next.jsの思想を先に学んでおくべきだった」にも繋がる話だと思っています。)

 本当は、

・チーム内でReact/Nextの基礎勉強会をする
・小さくベストプラクティスを共有する

 こういった準備をしておいたほうが、移行中の不安はかなり減ったはずです。

おわりに

 Vue.js/Nuxt.jsからReact/Next.jsへの移行は、単なるフレームワーク変更ではありませんでした。

 移行の過程では、思想の差、設計の差、学習コスト、役割分担、レビュー体制など、想像していた以上に多くの壁が立ちはだかりました。

 どれだけ優れたフレームワークでも、十分な理解なし開発すると技術の良さを引き出すことができません。

 逆に、手触りを掴み、設計を理解し、チームで共通認識をつくったうえで取り組めば、React/Next.jsの強みは確実に活きてきます。

 現在の私たちのチームでは学びを積み重ねながら改善が進んでおり、「理解しながら進めること」がどれほど大切かをあらためて実感しています。

 この記事が、これから移行を検討する人や、同じような壁に直面している人にとって、少しでも判断材料や安心材料になれば嬉しいです。

江藤皓史/FIXER
有明高専卒24入社
チョコミント/TRPG/まーちゃんごめんね

カテゴリートップへ

この連載の記事