「Laravel Inertia」で検索すると、そもそもInertiaが何をするツールなのか、Vue・Reactのどちらを選べばよいのか、実際にどうインストールすればよいのかがバラバラに解説されていて分かりにくいことがあります。この記事では、Laravel 13.20.0 + PHP 8.3の環境で実際にComposer・npmのコマンドを実行して検証した手順に沿って、Inertiaの概念から導入、React/Vueでのページ作成、つまずきやすいポイントまでを一つの流れでまとめます。
Laravel Inertiaとは何か
Inertia(Inertia.js)は、LaravelのようなサーバーサイドフレームワークとVue・Reactなどのクライアントサイドフレームワークを「接着剤」のようにつなぐライブラリです。REST APIやGraphQLを別途構築しなくても、コントローラーからJSONで初期データを返すだけで、SPA(Single Page Application)のようにページ遷移がリロードなしで行われる画面を作れます。
公式サイトではInertiaのアプローチを「モダンモノリス(Modern Monolith)」と呼んでいます。バックエンドとフロントエンドを別々のリポジトリ・別々のAPIで分離するのではなく、Laravelのroutes/web.phpやコントローラー、認証・バリデーションといった仕組みをそのまま使いながら、画面描画だけをVue・Reactに任せる考え方です。
Inertiaが解決する課題
- SPAを作るたびにAPIエンドポイントを設計・実装する手間をなくす
- Laravelの認証(Breeze/Fortify)やCSRF保護、バリデーションエラーの表示をそのまま流用する
- ページ遷移時のフルリロードをなくし、状態を保持したままスムーズな画面遷移を実現する
Livewireとの違い
同じくLaravelでSPAのようなUXを作る仕組みにLaravel Livewireがありますが、両者は画面描画の主体が異なります。Livewireは画面の描画をPHP(Blade)側が担当し、操作のたびにAjaxでサーバーへ問い合わせて差分を反映します。一方Inertiaは、画面の描画自体をVue・Reactなどクライアントサイドのコンポーネントが担当し、Laravel側は各ページに必要なデータをJSONで渡すだけです。JavaScriptフレームワークの経験があり、コンポーネント志向でUIを組みたい場合はInertia、PHPだけで完結させたい場合はLivewireが向いています。
Inertiaのメリット・デメリット
| メリット | デメリット | |
|---|---|---|
| 設計面 | API層を作らずにLaravelのルーティング・コントローラーをそのまま使える | Vue・Reactの基礎知識が必要になる |
| UX | ページ遷移がリロードなしで行われ、SPAのような操作感になる | 初回描画はクライアントサイドレンダリングのため、SEOを重視するページには別途対応が必要 |
| 開発体験 | バリデーションエラーや認証状態をLaravel側の仕組みのまま扱える | コンポーネント単位の状態管理など、フロントエンドの設計知識が求められる |
検証環境
この記事のコマンド・コードはすべて次の環境で実際に実行し、動作を確認しています。
- Laravel Framework 13.20.0(PHP 8.3.32)
- inertiajs/inertia-laravel ^3.1
- @inertiajs/react ^3.6 または @inertiajs/vue3 ^3.6(npm最新版。旧パッケージの@inertiajs/inertia-react・@inertiajs/inertia-vueは0.8系で更新が止まっており非推奨)
- Node.js(npm 10系相当)
Laravel Inertiaのインストール手順
1. Laravelプロジェクトの作成
新規にLaravelプロジェクトを作る場合は、次のコマンドで作成します。
composer create-project laravel/laravel your-project-name
cd your-project-name
現行のLaravelインストーラーは、プロジェクト作成時に.envの生成、APP_KEYの発行、SQLiteデータベースファイルの作成、初期マイグレーションまでを自動で行います。既存プロジェクトにInertiaを追加する場合は、この手順は不要です。
2. Inertiaのサーバーサイドアダプターを追加
composer require inertiajs/inertia-laravel
続けて、Inertia用のミドルウェアをArtisanコマンドで生成します。
php artisan inertia:middleware
実行するとapp/Http/Middleware/HandleInertiaRequests.phpが作成されます。生成されたミドルウェアを、Laravel 11以降のミドルウェア登録方式に合わせてbootstrap/app.phpのwebグループに追加します。
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
]);
})
3-A. Reactを使う場合のフロントエンド設定
npm install @inertiajs/react react react-dom
npm install -D @vitejs/plugin-react
vite.config.jsにReactプラグインを追加し、エントリーファイルをapp.jsxに変更します。
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.jsx'],
refresh: true,
}),
react(),
],
});
resources/js/app.jsxを作成し、Inertiaアプリの初期化処理を書きます。
// resources/js/app.jsx
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true });
return pages[`./Pages/${name}.jsx`];
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />);
},
});
3-B. Vueを使う場合のフロントエンド設定
npm install @inertiajs/vue3 vue
npm install -D @vitejs/plugin-vue
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
vue(),
],
});
resources/js/app.jsには次のように書きます。旧バージョンにあったInertiaAppコンポーネントやVue 2形式のnew Vue()はVue3系ではすでに使われないため、createApp + createInertiaAppの形式で統一します。
// resources/js/app.js
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.vue', { eager: true });
return pages[`./Pages/${name}.vue`];
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el);
},
});
アダプター(VueやNode.jsのバージョンとの相性など)が原点で分からなくなった場合は、LaravelとVue.jsの連携も参考にしてください。
4. ルートテンプレートの用意
ReactでもVueでも共通で、Inertia用のBladeテンプレート(ルートビュー)を1枚だけ用意します。resources/views/app.blade.phpを作成し、Laravelの標準テンプレート(welcome.blade.phpなど)とは別に用意する点に注意してください。
{{-- resources/views/app.blade.php --}}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@vite(['resources/css/app.css', 'resources/js/app.jsx'])
@inertiaHead
</head>
<body>
@inertia
</body>
</html>
Vue版を使う場合は@viteの指定をresources/js/app.jsに読み替えてください。HandleInertiaRequestsミドルウェアの$rootViewプロパティが'app'を指しているため、このファイル名を変更した場合はミドルウェア側の設定も合わせて変更します。
ルーティングとページコンポーネントの実装
ルート・コントローラーの設定
routes/web.phpでは、通常のview()の代わりにInertia::render()を使ってページコンポーネント名とpropsを指定します。
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::get('/home', function () {
return Inertia::render('Home', [
'message' => 'Hello from Laravel',
]);
});
コントローラーから返す場合も書き方は同じです。
namespace App\Http\Controllers;
use Inertia\Inertia;
use Inertia\Response;
class HomeController extends Controller
{
public function index(): Response
{
return Inertia::render('Home', [
'message' => 'Hello from Laravel',
]);
}
}
Inertia::render('Home', ...)の'Home'は、resources/js/Pages/ディレクトリ内のコンポーネントファイル名(拡張子なし)と一致させます。
ページコンポーネントでpropsを受け取る
Reactの場合はresources/js/Pages/Home.jsxを作成します。
export default function Home({ message }) {
return (
<div>
<h1>Home Component</h1>
<p>Received: {message}</p>
</div>
);
}
Vueの場合はresources/js/Pages/Home.vueを作成します。
<script setup>
defineProps({
message: String,
});
</script>
<template>
<div>
<h1>Home Component</h1>
<p>Received: {{ message }}</p>
</div>
</template>
ここまでの手順でnpm run buildを実行し、php artisan serveでアクセスすると、コントローラーで渡したmessageの値が画面に表示されます。実際にX-Inertia: trueヘッダーを付けてリクエストすると、ページ全体ではなく次のようなJSONだけが返ってくることも確認できます。
curl http://127.0.0.1:8000/home -H "X-Inertia: true" -H "X-Inertia-Version: <現在のバージョン値>"
# {"component":"Home","props":{"errors":{},"message":"Hello from Laravel"},"url":"/home", ... }
この「HTML全体ではなくpropsだけを含むJSONを返す」仕組みが、Inertiaがページ遷移をリロードなしで実現している正体です。
フォーム送信を扱う(useForm)
実務でよく使うのが、フォームの送信状態やバリデーションエラーをまとめて扱えるuseFormヘルパーです。ReactでもVueでも同じ考え方で使えます。
// React(resources/js/Pages/Contact.jsx)
import { useForm } from '@inertiajs/react';
export default function Contact() {
const { data, setData, post, processing, errors } = useForm({
name: '',
email: '',
});
function submit(e) {
e.preventDefault();
post('/contact');
}
return (
<form onSubmit={submit}>
<input value={data.name} onChange={(e) => setData('name', e.target.value)} />
{errors.name && <div>{errors.name}</div>}
<button type="submit" disabled={processing}>送信</button>
</form>
);
}
Laravel側のコントローラーは通常のバリデーションをそのまま書くだけで、失敗時のエラーは自動的にerrorspropとしてフロントエンドに渡されます。
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
// 保存処理
return redirect('/contact')->with('success', '送信しました');
}
つまずきやすいポイント
アセットのバージョンが変わると強制的にフルリロードされる
npm run buildのたびにアセットのバージョンハッシュが変わります。ブラウザが保持している古いバージョン値でInertiaリクエストを送ると、Laravel側は409 ConflictとX-Inertia-Locationヘッダーを返し、Inertiaのクライアントはこれを検知して自動的にフルページリロードを行います。デプロイ直後にユーザーの画面が一瞬白くなるのはこのためで、異常ではありません。
419エラー(CSRFトークン切れ)
フォーム送信時に419エラーが出る場合、セッションの有効期限が切れているか、XSRF-TOKENクッキーが送信されていない状態です。Inertiaの@inertiajs/react・@inertiajs/vue3はaxiosベースでXSRF-TOKENクッキーを自動送信しますが、サブドメインを跨ぐ構成やSPA的なドメイン分離をしている場合は、SESSION_DOMAIN・CookieのSameSite設定を見直してください。
コンポーネント名の不一致
Inertia::render('Home', ...)の第一引数と、resolve関数内で読み込むファイルパス(./Pages/${name}.jsxなど)が一致していないと、ブラウザのコンソールに「Page not found」といったエラーが出ます。サブディレクトリを使う場合はInertia::render('Admin/Dashboard', ...)のようにスラッシュ区切りで指定し、対応するファイルもresources/js/Pages/Admin/Dashboard.jsxに置きます。
まとめ
Laravel Inertiaは、API層を新たに設計しなくても、Laravelの既存の仕組み(ルーティング・認証・バリデーション)を活かしたままVue・ReactでSPAのようなUIを作れる仕組みです。導入自体はcomposer require inertiajs/inertia-laravelとnpm install @inertiajs/react(またはvue3)の2ステップで完了し、あとはInertia::render()でページコンポーネントにpropsを渡すだけで画面を組み立てられます。まずは1ページだけ試験的に導入し、フォーム送信やバリデーションエラーの表示まで実際に動かして挙動を確認してから、既存画面の置き換えを進めるとスムーズです。

コメント