LaravelとReactは競合する技術ではありません。LaravelはPHPで書くサーバー側のフレームワーク、ReactはブラウザのUIを組むJavaScriptライブラリで、実務で決めるのは「どちらを使うか」ではなく「どうつなぐか」です。この記事では、つなぎ方を3つの構成に分け、2026年9月28日時点の公式Reactスターターキット(Laravel 13.33.0・Inertia 3・React 19.3.0)で手順とサーバーの応答を確かめた結果をもとに、構成の選び方から認証・SSR・デプロイまでを整理します。
まとめ:LaravelとReactの連携構成の選び方
- 新規にWebアプリを作るなら、公式Reactスターターキット(Inertia)が第一候補です。APIを作らずにコントローラーからReactのページへデータを渡せます。
- Next.jsなど別アプリのReactや、モバイルアプリも同じバックエンドを使うなら、LaravelをAPIにしてSanctumで認証します。Cookie認証は
statefulApi()を足さないと401になります。 - 既存のBladeアプリに一部だけReactを入れるなら、
@vitejs/plugin-reactと@viteReactRefreshで部分導入します。 - スターターキットを
composer create-projectで直接作るときは--stability=devが必要です。付けないと2025年2月の古い版が入ります。 - 検索に表示されるページでInertiaを使うなら、SSR(
npm run build:ssrとphp artisan inertia:start-ssr)を有効にします。
以下、役割分担の整理から順に、各構成の手順と落とし穴を説明します。
LaravelとReactの役割分担と「laravel vs react」の比較軸
LaravelとReactを比較するときは、サーバー側とUI側という役割の違いを押さえます。LaravelはルーティングやEloquent(データベース操作)、認証、キュー、メール送信を担うサーバー側の土台です。本記事のInertia・Blade部分導入では、Reactが画面の描画と状態管理を担当し、データベース操作やセッション管理はLaravel側に置きます。
比べるべきなのは「Laravelの画面を何で書くか」です。Laravel 13の公式ドキュメント(Frontend)は、PHPで書く道としてBladeとLivewire、JavaScriptで書く道としてInertia経由のReact・Vue・Svelteを挙げています。
| 画面の書き方 | 主に書く言語 | 公式スターターキット | 向くチーム |
|---|---|---|---|
| Blade | PHP(テンプレート) | なし(素のLaravel) | 画面の動きが少ない業務画面 |
| Livewire | PHP | あり | PHP中心でJSを書きたくない |
| React(Inertia) | TypeScript | あり | React経験者がいる・部品が多い |
| Vue(Inertia) | TypeScript | あり | Vue経験者がいる |
| Svelte(Inertia) | TypeScript | あり | Svelte 5を使いたい |
React・Vue・Svelteの3つはどれもInertia 3の上に載るので、Laravel側のコントローラーやルートの書き方は同じです。差が出るのはフロントのエコシステムで、UI部品のshadcn/uiやReact向けライブラリを使いたいならReact、チームにVueの経験者が多いならVueを選びます。JavaScriptを書く人がいないなら、Reactを無理に入れずにLivewire(v4)を選ぶ方が保守しやすくなります。
3つの連携構成の比較(Inertia・API分離・Blade部分導入)
LaravelとReactのつなぎ方は、ルーティングをどちらが持つかで3つに分かれます。
| 比較軸 | Inertia(スターターキット) | API分離+Sanctum | Blade部分導入 |
|---|---|---|---|
| ルーティング | Laravel | React側(Next.js等) | Laravel |
| データの渡し方 | props(JSON) | REST API | data属性など |
| 認証 | セッション(Fortify) | Sanctum | セッション |
| リポジトリ | 通常1つ | 同一でも分割でも可 | 通常1つ |
| APIの設計 | 不要 | 必要 | 不要(必要なら追加) |
| モバイルアプリとの共用 | 不向き | 向く | 不向き |
判断の基準は2つだけです。1つ目は、Laravel以外のクライアント(スマホアプリ、別チームのNext.js、外部連携)が同じデータを読むかどうかです。読むなら外部クライアント向けのAPIが必要ですが、Web画面はInertiaのまま併用できます。Web画面自体を別アプリとして開発・配信する必要がなければ、Inertiaで画面用APIやCORS設定を省けます。2つ目は、既にBladeで動いている画面がどれだけあるかです。大半がBladeで、Reactにしたいのが検索フォームやグラフなど数か所なら、部分導入で足ります。
Reactスターターキットで始める手順とバージョン
laravel newでの作成と起動コマンド
PHP 8.3以上とComposerのほか、Node.jsが必要です。スターターキットのビルドに使うvite-plus 0.3.0は、package.json の engines で Node.js ^20.19.0 || ^22.18.0 || >=24.11.0 を指定しています。
公式ドキュメント(Starter Kits 13.x)の手順は、Laravelインストーラーを入れて laravel new を実行し、対話でスターターキットにReactを選ぶ流れです。認証の選択肢は、Laravel組み込みの認証(Fortify)とWorkOS AuthKitの2つです。
composer global require laravel/installer
laravel new my-app
cd my-app
npm install && npm run build
composer run dev
composer run dev の中身は php artisan dev で、php artisan dev:list で見ると、開発サーバー・キューワーカー・ログ表示(Pail)・Viteの4プロセスを同時に起動します。ブラウザで http://localhost:8000 を開けば、ログイン・登録・パスワード再設定・二要素認証・パスキーまで入った状態から始められます。
composer create-projectで直接作るとv1.0.1が入る落とし穴
インストーラーを使わず composer create-project laravel/react-starter-kit my-app と打つと、Composerは安定版タグの v1.0.1 を選びます。このタグは2025年2月24日のもので、PHPUnit 11やZiggyを使っていた頃の構成です。スターターキットは現在タグを切らずにmainブランチで更新されているため、現行版はdev扱いになっています。
インストーラー(laravel/installer v5.32.0)のソースを読むと、スターターキットの作成時に --stability=dev を付けています。手でComposerを叩く場合も同じオプションを付けてください。
composer create-project laravel/react-starter-kit my-app --stability=dev
この方法で作ると、作成直後に走る php artisan install:features が composer コマンドを呼びます。composer.phar だけを置いていてPATHに composer が無い環境では、ここが終了コード127で止まり、APP_KEY の生成とマイグレーションが実行されません。その場合は php artisan key:generate と php artisan migrate を手で実行します。
同梱パッケージのバージョン(2026年9月28日時点)
mainブランチ(コミット717b8f5)から作成した環境で、実際に入った版は次のとおりです。スターターキットの composer.json と package.json は ^ 付きの範囲指定なので、作成する日によって版は上がります。
| パッケージ | 入った版 | 役割 |
|---|---|---|
| laravel/framework | 13.33.0 | 本体 |
| inertiajs/inertia-laravel | 3.4.0 | サーバー側アダプター |
| @inertiajs/react | 3.7.1 | クライアント側アダプター |
| laravel/fortify | 1.40.0 | 認証バックエンド |
| react | 19.3.0 | UI |
| vite / vite-plus | 8.3.1 / 0.3.0 | ビルド |
| tailwindcss | 4.3.3 | CSS |
| typescript | 5.9.3 | 型チェック |
Laravel 13のサポート期限はLaravelの最新バージョンは13|対応PHPバージョン・サポート期限一覧とアップグレード判断にまとめています。注意したいのは、このスターターキットにSanctumも routes/api.php も含まれていない点です。APIが必要になったら後述の install:api で足します。
スターターキットのディレクトリ構成と各ファイルの役割
「LaravelとReactのファイルをどこに置くか」は、スターターキットの配置に合わせるのがいちばん揉めません。フロントのコードはすべて resources/js の下に入ります。
app/Http/Middleware/HandleInertiaRequests.php # 全ページ共通のprops
config/inertia.php # SSRの接続先など
routes/web.php, routes/settings.php # 画面のルート
resources/views/app.blade.php # 唯一のBlade(@viteReactRefresh・@vite)
resources/js/
├── app.tsx # createInertiaApp とレイアウトの割り当て
├── pages/ # Inertia::render の名前と1対1のページ
├── components/ # 再利用部品(components/ui は shadcn/ui)
├── layouts/ # app・auth・settings のレイアウト
├── hooks/ # 自作フック
├── lib/ # ユーティリティ
├── types/ # 型定義
├── actions/, routes/, wayfinder/ # Wayfinderの生成物
運用で守るべきことは3つあります。まず、pages/ のファイルパスは Inertia::render('posts/index') の名前とそのまま対応するので、URLの階層に合わせて切ります。次に、actions/・routes/・wayfinder/ はLaravelのルート定義からWayfinderが生成する型付きのURL関数なので手で編集しません(vite.config.ts のlint対象からも外されています)。最後に、Fortifyの機能(メール認証など)を無効にするときは、resources/js 側の参照も消します。公式ドキュメントのとおり、消し忘れると生成されないルートを参照してビルドが失敗します。
InertiaでLaravelからReactへデータを渡す仕組み
Inertia::renderとpropsの書き方
Inertia構成では、コントローラーがJSONのAPIを返す代わりに、ページ名とpropsを返します。スターターキットに次のルートとページを足すと、/posts に一覧が表示されます。
// routes/web.php
use Inertia\Inertia;
Route::get('/posts', function () {
return Inertia::render('posts/index', [
'posts' => [
['id' => 1, 'title' => 'Hello'],
['id' => 2, 'title' => 'World'],
],
]);
});
// resources/js/pages/posts/index.tsx
type Post = { id: number; title: string };
export default function Index({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
ルートにデータを渡さない画面なら、スターターキットの routes/web.php と同じく Route::inertia('dashboard', 'dashboard') の1行で済みます。
初回HTMLと2回目以降のJSON応答
上の /posts に curl でリクエストすると、Inertiaの動き方がそのまま見えます。
| リクエスト | ステータス | 返るもの |
|---|---|---|
| 通常のGET(初回表示) | 200 | HTML+data-page のJSON |
| X-Inertia: true+一致するX-Inertia-Version | 200 | JSONのみ |
| X-Inertia: true(版なし・不一致) | 409 | X-Inertia-Location ヘッダー |
| 一致する版のInertiaリクエスト+Partial-ComponentとPartial-Data | 200 | posts と errors だけ |
部分リロードの行は、X-Inertia: true・一致する X-Inertia-Version・X-Inertia-Partial-Component: posts/index・X-Inertia-Partial-Data: posts をまとめて送った結果です。
初回はHTMLの中の <script data-page="app" type="application/json"> にページ名とpropsが埋め込まれ、以降の画面遷移ではJSONだけがやり取りされます。409は、デプロイでビルド成果物のハッシュが変わったときにブラウザ側で全体を読み直させるための応答です。propsには HandleInertiaRequests::share() で定義した共通値(スターターキットでは name・auth.user・sidebarOpen)が通常のページ応答に含まれます。ただし、部分リロードでは対象外の共有値を省略できます。ログインユーザーの全カラムをそのまま載せず、共有する項目は絞ってください。Inertia 3の変更点と仕組みの詳細はLaravel Inertiaとは|APIを作らずSPAを組む仕組みと3系の変更点で扱っています。
別アプリのReact(Next.js・Vite SPA)とSanctumで組むAPI分離構成
Laravel 13のインストールガイドは、Next.jsのようなJavaScriptアプリのAPIバックエンドとしてLaravelを使い、認証にSanctumを使う構成を案内しています。なお、公式のNext.js用ひな形だった laravel/breeze-next は2026年6月4日にアーカイブされ、現在は公式のNext.js向けスターターキットがありません。フロント側は自分で組む前提です。
install:apiで追加されるものと追加されないもの
スターターキットに php artisan install:api を実行すると、次のものが追加されます。
routes/api.php(auth:sanctum付きの/userルートだけが入った状態)config/sanctum.phpとpersonal_access_tokensテーブルのマイグレーション- laravel/sanctum(2026年9月28日時点で v4.3.3)
一方で、SPAのCookie認証に必要な statefulApi() は追加されません。config/cors.php も既定では存在しないので、php artisan config:publish cors で書き出してから編集します(書き出した直後の supports_credentials は false です)。API全体の設計はLaravel APIの作り方|install:apiとAPIリソース・エラー応答の統一を参照してください。
SPAのCookie認証を通す設定
Sanctumの公式ドキュメントでは、SPAとAPIが同じトップレベルドメインを共有することが前提です(app.example.com と api.example.com のようなサブドメイン違いは可)。そのうえで、次の4か所を設定します。
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->statefulApi();
})
# .env
SANCTUM_STATEFUL_DOMAINS=app.example.com
SESSION_DOMAIN=.example.com
// config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login'],
'allowed_origins' => ['https://app.example.com'],
'supports_credentials' => true,
config/cors.php の既定の paths は api/* と sanctum/csrf-cookie だけなので、別オリジンから呼ぶ login(Fortifyなら logout なども)を足します。React側では、ログインの前に /sanctum/csrf-cookie を呼んでCSRF用のCookieを受け取ります。Axiosなら withCredentials と withXSRFToken を有効にします。
import axios from 'axios';
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
export async function login(email: string, password: string) {
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', { email, password });
const { data } = await axios.get('/api/user');
return data;
}
/login はSanctumが用意するルートではありません。スターターキットならFortifyのルートがそのまま使え、API専用のLaravelなら自分でログイン用のルートを書きます。SANCTUM_STATEFUL_DOMAINS を指定しない場合の既定値には localhost・localhost:3000・127.0.0.1:8000 と APP_URL のホストが入っているので、ローカルのNext.js(ポート3000)はそのまま通ります。
statefulApiの有無で変わる応答
スターターキットに install:api を入れ、Originを http://localhost:3000 にしてCookieを保持したまま上の3リクエストを順に送ると、結果は次のように分かれます。
| 条件 | csrf-cookie | login | /api/user |
|---|---|---|---|
| statefulApi なし | 204 | 200 | 401 |
| statefulApi あり | 204 | 200 | 200 |
| statefulApi あり・stateful外のOrigin | 204 | 200 | 401 |
ログインは成功しているのにAPIだけが Unauthenticated. を返す場合があります。その場合は、ログイン処理よりも先に statefulApi() の有無と、フロントのドメインが SANCTUM_STATEFUL_DOMAINS に入っているかを確認してください。
トークン認証とPassportを選ぶ場面
SanctumのSPA用Cookie認証は、共通の親ドメインを持つWebフロントが対象です。自社SPAは同じ親ドメイン配下に配置する構成を優先し、以下のトークン認証はスマホアプリなどを対象にします。その場合はUserモデルに HasApiTokens を付け、createToken() で発行したトークンを Authorization: Bearer ヘッダーで送ります。Passportは、OAuth2の認可フローが必要な場合に選びます。第三者アプリへの権限委譲に加え、クライアントクレデンシャルによるマシン間認証なども対象です。自社のReact画面にログインさせる目的でPassportを入れると、使わないOAuthクライアント管理まで抱えることになります。Cookieとトークンの使い分けはLaravel Sanctumとは:SPAとモバイルのトークン認証とCookieセッションの使い分けで詳しく比較しています。
既存のBladeアプリにReactを部分導入する手順
画面の大半をBladeのまま残し、いいねボタンや検索フォームだけをReactにする構成です。以下は、コントローラーからBladeへ $post を渡し、postId と count を受け取る LikeButton コンポーネントを用意済みという前提の例です。Laravelの公式ドキュメント(Vite 13.x)の手順に沿って、プラグインとReactを入れます。
npm install --save-dev @vitejs/plugin-react
npm install react react-dom
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
laravel(['resources/js/app.jsx']),
react(),
],
});
Bladeのレイアウトでは、@viteReactRefresh を @vite より前に書きます(公式ドキュメントがこの順序を指定しています)。Reactに渡す値は json_encode() でJSONにし、{{ }} でダブルクォートの属性に出力します。{{ }} が引用符や < をHTMLエスケープするので、値にタグが含まれても属性は壊れません。@json([...]) で配列の中にカンマを書くと、Bladeがカンマを引数区切りと解釈し、エスケープ指定の外れた json_encode([...], 512) にコンパイルされる点に注意してください(Laravel 13.33.0で確認)。
{{-- resources/views/layouts/app.blade.php --}}
@viteReactRefresh
@vite('resources/js/app.jsx')
{{-- 埋め込みたい場所 --}}
<div id="like-button" data-props="{{ json_encode(['postId' => $post->id, 'count' => $post->likes_count]) }}"></div>
// resources/js/app.jsx
import { createRoot } from 'react-dom/client';
import LikeButton from './components/LikeButton';
const el = document.getElementById('like-button');
if (el) {
createRoot(el).render(<LikeButton {...JSON.parse(el.dataset.props)} />);
}
ファイル拡張子は .jsx か .tsx にし、設定後は npm run dev で開発サーバーを起動するか npm run build でビルドします。Reactの部品が複数ページに広がり、ページ遷移まで自前で書き始めたら、Inertiaへ移るタイミングです。@vite の書き方やビルド時の注意はLaravel Viteの使い方|@viteの書き方と本番ビルドの落とし穴にまとめています。
SSRと本番デプロイで必要な設定
Inertia SSRの有効化と起動コマンド
InertiaのSSRを使うのに、Next.jsは要りません。スターターキットの config/inertia.php は最初から 'ssr' => ['enabled' => true, 'url' => 'http://127.0.0.1:13714'] になっていて、SSR用のバンドルを作ってNodeのSSRサーバーを起動するだけで有効になります。
npm run build:ssr # bootstrap/ssr/app.js を生成
php artisan inertia:start-ssr # 13714番ポートでSSRサーバーを起動
php artisan inertia:check-ssr # "Inertia SSR server is running." を確認
SSRサーバーを動かした状態で /posts を取得すると、サイドバーや一覧の <li> がサーバー側で描画済みのHTMLとして返ります。公式ドキュメント(Starter Kits 13.x)には composer dev:ssr というコマンドも載っていますが、2026年9月28日時点のmainブランチの composer.json にはこのスクリプトがありません。上の2つのコマンドを使ってください。
本番環境へのデプロイ手順
Inertia構成でも部分導入でも、サーバーに置くのは1つのLaravelアプリです。ビルドはデプロイ時に1回だけ行い、public/build を配信します。
composer install --no-dev --optimize-autoloader
npm ci
npm run build # SSRを使うなら npm run build:ssr
php artisan migrate --force
php artisan optimize
SSRを使う場合は、php artisan inertia:start-ssr をSupervisorやsystemdで常駐させ、デプロイのたびに再起動します。ビルドのハッシュが変わると、開いたままのブラウザには前述の409が返って自動で再読み込みされるので、利用者に手動での更新を求める必要はありません。API分離構成では、LaravelとReactを別々にデプロイし、Laravel側の SANCTUM_STATEFUL_DOMAINS と SESSION_DOMAIN を本番のドメインに書き換えます。
React連携を採用しない方がよい場面と失敗パターン
React連携は、条件が合わない案件に入れると保守費用だけが増えます。次のどれかに当てはまるなら、構成を見直してください。
- PHPしか書けないチームの管理画面:Reactの依存更新(React・Vite・Tailwind・shadcn/ui)を追い続ける人がいないなら、BladeかLivewireを選びます。
- 検索流入が要る公開ページをSSRなしのInertiaで作る:初回HTMLに入るのはpropsのJSONで、本文はJavaScriptの実行後に描画されます。公開ページはSSRを有効にするか、Bladeで書きます。
- SPAとAPIのドメインが別(例:
shop-a.comとapi.shop-b.net):SanctumのCookie認証は使えません。自社SPAでは、APIをSPAと同じ親ドメイン配下に置く構成を優先します。別ドメインが必須なら、認証方式とトークンの保持場所を別途設計します。 - 「将来モバイルアプリを作るかも」という理由だけでAPI分離にする:API・CORS・認証を最初から2系統持つことになります。Inertiaで始めても、必要になった時点で
install:apiでAPIを追加できます。
よくある質問
LaravelとReactはどちらを選ぶべきですか?
どちらか一方を選ぶものではありません。LaravelはPHPでデータベース・認証・ルーティングを扱うサーバー側のフレームワーク、ReactはブラウザのUIを組むライブラリで、両方を組み合わせて使います。実際に決めるのは「Laravelの画面をBlade・Livewire・React・Vueのどれで書くか」で、JavaScriptを書く人がいてUI部品が多いならReact、PHP中心のチームならLivewireが扱いやすい選択です。
LaravelのフロントエンドはReactとVueのどちらがよいですか?
Laravel側の差はほぼありません。公式のReact・Vueスターターキットはどちらも Inertia 3・TypeScript・Tailwind・shadcn系のUIで構成され、コントローラーやルートの書き方は同じです。決め手はチームの経験と使いたいライブラリです。React向けのUIライブラリ(shadcn/uiなど)をそのまま使いたいならReact、テンプレート記法に慣れた人が多いならVueを選びます。
Laravel BladeとReactはどう使い分けますか?
ページ全体を描き直して困らない画面はBlade、入力に応じて画面の一部が頻繁に変わる画面はReactが向きます。混在するなら、Viteに @vitejs/plugin-react を足し、Bladeで @viteReactRefresh を @vite より前に書けば、既存画面を残したまま数か所だけReactにできます。
LaravelでReactのSPAを作るにはAPIが必要ですか?
Inertiaを使うなら不要です。コントローラーが Inertia::render() でページ名とpropsを返し、画面遷移時はInertiaがJSONだけを取得します。APIが必要になるのは、Next.jsなど別アプリのReactやスマホアプリが同じデータを読む場合で、php artisan install:api でSanctumを入れ、SPAのCookie認証なら statefulApi() も追加します。
Laravel BreezeやReactプリセットは今も使えますか?
laravel/breeze の v2.4.2(2026年5月19日)はLaravel 13へのインストールを許容していますが、Laravel 13の公式ドキュメントが新規作成で案内しているのは新しいReactスターターキットです。laravel/ui の php artisan ui react で入るReactプリセットも、Inertiaを使わない旧来の構成です。これから作るなら laravel new でReactスターターキットを選んでください。既存のBreeze環境の移行はLaravel Breeze・Jetstreamはなぜ非推奨に?で解説しています。