Laravel Inertiaとは?インストールからReact/Vueでの使い方まで徹底解説

実装・応用テクニック

「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 ConflictX-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-laravelnpm install @inertiajs/react(またはvue3)の2ステップで完了し、あとはInertia::render()でページコンポーネントにpropsを渡すだけで画面を組み立てられます。まずは1ページだけ試験的に導入し、フォーム送信やバリデーションエラーの表示まで実際に動かして挙動を確認してから、既存画面の置き換えを進めるとスムーズです。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント