0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel + React開発】これからの開発がInertia.jsで変わる!

Posted at

はじめに

ほぼ5年ぶりにブログを更新します。
今回は、Inertia.js(イネーシャ.js)というものを知ったので、ブログを書きました。
Inertia.jsというのは簡単に説明すると、「モダンSPAをサーバーサイドフレームワークだけで作れる仕組み」 です。つまり、SPA + APIの開発が不要というメリットがあります。Inertia.jsは、フロントエンドとバックエンドを繋ぐパイプライン的な立ち位置であり、ユーザー → Laravel → Inertia → React という構造の開発をすることができます。

本記事では、実際に構築したToDoアプリケーションのコードを元に、従来のLaravel + ReactLaravel + Inertia.js + Reactの違いを詳細に比較し、Inertia.jsのメリットを解説します。

※こちらの記事は開発した環境をもとに一部、AIでブログを生成しています。

比較対象のプロジェクト構成

共通の技術スタック

  • Backend: Laravel 11
  • Frontend: React 18
  • UI Library: Material-UI (MUI)
  • Build Tool: Vite
  • Database: PostgreSQL 15

主な違い

従来のアプローチではLaravel REST API + React SPAを使用しますが、Inertia.jsを使用することでモノリシックなLaravelアプリケーションとして構築できます。


1. 環境構築の違い

1.1 パッケージのインストール

従来のLaravel + React SPA

Backend (Laravel)

# LaravelをAPIモードでインストール
composer create-project laravel/laravel backend-api
cd backend-api

# CORS対応
composer require fruitcake/laravel-cors

# API認証(Sanctum)
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Frontend (React)

# 別ディレクトリにReactアプリを作成
npx create-react-app frontend
cd frontend

# 必要なパッケージをインストール
npm install axios react-router-dom @mui/material @mui/icons-material @emotion/react @emotion/styled

必要なファイル数: 2つのプロジェクト(backend-api、frontend)を別々に管理


Laravel + Inertia.js + React

統合インストール

# Laravelプロジェクトを作成
composer create-project laravel/laravel todo-app
cd todo-app

# Inertia.jsサーバーサイド
composer require inertiajs/inertia-laravel

# フロントエンド依存関係
npm install @inertiajs/react react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styled
npm install -D @vitejs/plugin-react

必要なファイル数: 1つのプロジェクト内で完結

差分:

  • ✅ プロジェクト構造がシンプル(1つのリポジトリ)
  • ✅ CORSやAPI認証の設定が不要
  • ✅ フロントエンドとバックエンドの依存関係が明確

1.2 設定ファイルの比較

従来のLaravel + React SPA

Backend: config/cors.php

<?php
return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['http://localhost:3000'], // ReactアプリのURL
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,
];

Backend: app/Http/Kernel.php

protected $middlewareGroups = [
    'api' => [
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

Frontend: .env

REACT_APP_API_URL=http://localhost:8000/api

Frontend: src/services/api.js

import axios from 'axios';

const api = axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    withCredentials: true,
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
    }
});

// CSRF トークン取得
export const getCsrfCookie = () => {
    return axios.get('http://localhost:8000/sanctum/csrf-cookie');
};

export default api;

Laravel + Inertia.js + React

bootstrap/app.php

<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
use App\Http\Middleware\HandleInertiaRequests;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
            HandleInertiaRequests::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

app/Http/Middleware/HandleInertiaRequests.php

<?php
namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    protected $rootView = 'app';

    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'errors' => function () use ($request) {
                return $request->session()->get('errors')
                    ? $request->session()->get('errors')->getBag('default')->getMessages()
                    : (object) [];
            },
        ]);
    }
}

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/js/app.jsx',
            refresh: true,
        }),
        react(),
    ],
});

差分:

  • ✅ CORS設定が不要(同一オリジン)
  • ✅ API認証の設定が不要(Laravelのセッション認証をそのまま使用)
  • ✅ CSRF保護が自動的に処理される
  • ✅ 設定ファイルが少ない

2. コード構造の違い

2.1 ルーティング

従来のLaravel + React SPA

Backend: routes/api.php

<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\TodoController;

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/todos', [TodoController::class, 'index']);
    Route::post('/todos', [TodoController::class, 'store']);
    Route::put('/todos/{todo}', [TodoController::class, 'update']);
    Route::delete('/todos/{todo}', [TodoController::class, 'destroy']);
});

Frontend: src/App.js

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import TodoList from './pages/TodoList';
import Login from './pages/Login';

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<Navigate to="/todos" />} />
                <Route path="/todos" element={<TodoList />} />
                <Route path="/login" element={<Login />} />
            </Routes>
        </BrowserRouter>
    );
}

export default App;

Laravel + Inertia.js + React

routes/web.php(1つのファイルで完結)

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoController;

Route::get('/', function () {
    return redirect()->route('todos.index');
});

Route::get('/todos', [TodoController::class, 'index'])->name('todos.index');
Route::post('/todos', [TodoController::class, 'store'])->name('todos.store');
Route::put('/todos/{todo}', [TodoController::class, 'update'])->name('todos.update');
Route::delete('/todos/{todo}', [TodoController::class, 'destroy'])->name('todos.destroy');

差分:

  • ✅ フロントエンドのルーティング設定が不要
  • ✅ ルートの定義が1箇所に集約
  • ✅ Laravelの名前付きルートをそのまま使用可能
  • ✅ react-router-domのインストールが不要

2.2 コントローラー(データの受け渡し)

従来のLaravel + React SPA

Backend: app/Http/Controllers/Api/TodoController.php

<?php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Todo;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class TodoController extends Controller
{
    /**
     * 一覧取得
     */
    public function index(): JsonResponse
    {
        $todos = Todo::orderBy('created_at', 'desc')->get();
        
        return response()->json([
            'data' => $todos,
            'message' => 'Success'
        ], 200);
    }

    /**
     * 作成
     */
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
        ]);

        $todo = Todo::create([
            'title' => $validated['title'],
            'completed' => false,
        ]);

        return response()->json([
            'data' => $todo,
            'message' => 'Todo created successfully'
        ], 201);
    }

    /**
     * 更新
     */
    public function update(Request $request, Todo $todo): JsonResponse
    {
        $validated = $request->validate([
            'title' => 'sometimes|string|max:255',
            'completed' => 'sometimes|boolean',
        ]);

        $todo->update($validated);

        return response()->json([
            'data' => $todo,
            'message' => 'Todo updated successfully'
        ], 200);
    }

    /**
     * 削除
     */
    public function destroy(Todo $todo): JsonResponse
    {
        $todo->delete();

        return response()->json([
            'message' => 'Todo deleted successfully'
        ], 200);
    }
}

Laravel + Inertia.js + React

app/Http/Controllers/TodoController.php

<?php
namespace App\Http\Controllers;

use App\Models\Todo;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class TodoController extends Controller
{
    /**
     * 一覧表示
     */
    public function index(): Response
    {
        $todos = Todo::orderBy('created_at', 'desc')->get();

        return Inertia::render('Todos/Index', [
            'todos' => $todos
        ]);
    }

    /**
     * 作成
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
        ]);

        Todo::create([
            'title' => $validated['title'],
            'completed' => false,
        ]);

        return back();
    }

    /**
     * 更新
     */
    public function update(Request $request, Todo $todo)
    {
        $validated = $request->validate([
            'title' => 'sometimes|string|max:255',
            'completed' => 'sometimes|boolean',
        ]);

        $todo->update($validated);

        return back();
    }

    /**
     * 削除
     */
    public function destroy(Todo $todo)
    {
        $todo->delete();

        return back();
    }
}

差分:

  • ✅ JSONレスポンスの構築が不要
  • Inertia::render()で直接Reactコンポーネントにデータを渡せる
  • back()で前のページに戻るだけでOK(リダイレクト処理がシンプル)
  • ✅ HTTPステータスコードやレスポンス構造を気にする必要がない
  • ✅ コード量が約40%削減

2.3 フロントエンド(Reactコンポーネント)

従来のLaravel + React SPA

Frontend: src/pages/TodoList.jsx

import React, { useState, useEffect } from 'react';
import axios from '../services/api';
import {
    Container,
    Typography,
    TextField,
    Button,
    List,
    ListItem,
    Checkbox,
    IconButton,
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';

export default function TodoList() {
    const [todos, setTodos] = useState([]);
    const [title, setTitle] = useState('');
    const [loading, setLoading] = useState(false);
    const [errors, setErrors] = useState({});

    // 初回ロード時にデータ取得
    useEffect(() => {
        fetchTodos();
    }, []);

    // ToDo一覧を取得
    const fetchTodos = async () => {
        try {
            setLoading(true);
            const response = await axios.get('/todos');
            setTodos(response.data.data);
        } catch (error) {
            console.error('Error fetching todos:', error);
            alert('Failed to fetch todos');
        } finally {
            setLoading(false);
        }
    };

    // ToDo作成
    const handleSubmit = async (e) => {
        e.preventDefault();
        setErrors({});
        
        try {
            setLoading(true);
            await axios.post('/todos', { title });
            setTitle('');
            await fetchTodos(); // 再取得
        } catch (error) {
            if (error.response?.data?.errors) {
                setErrors(error.response.data.errors);
            }
        } finally {
            setLoading(false);
        }
    };

    // 完了状態をトグル
    const handleToggleComplete = async (todo) => {
        try {
            await axios.put(`/todos/${todo.id}`, {
                completed: !todo.completed
            });
            await fetchTodos(); // 再取得
        } catch (error) {
            console.error('Error updating todo:', error);
        }
    };

    // 削除
    const handleDelete = async (todoId) => {
        if (!window.confirm('このToDoを削除しますか?')) return;
        
        try {
            await axios.delete(`/todos/${todoId}`);
            await fetchTodos(); // 再取得
        } catch (error) {
            console.error('Error deleting todo:', error);
        }
    };

    if (loading && todos.length === 0) {
        return <div>Loading...</div>;
    }

    return (
        <Container maxWidth="md" sx={{ mt: 4 }}>
            <Typography variant="h3" component="h1" gutterBottom>
                ToDo アプリ
            </Typography>

            <form onSubmit={handleSubmit}>
                <TextField
                    fullWidth
                    label="新しいToDoを入力"
                    value={title}
                    onChange={(e) => setTitle(e.target.value)}
                    error={!!errors.title}
                    helperText={errors.title?.[0]}
                    disabled={loading}
                />
                <Button
                    type="submit"
                    variant="contained"
                    disabled={loading || !title.trim()}
                >
                    追加
                </Button>
            </form>

            <List>
                {todos.map((todo) => (
                    <ListItem key={todo.id}>
                        <Checkbox
                            checked={todo.completed}
                            onChange={() => handleToggleComplete(todo)}
                        />
                        <span style={{
                            textDecoration: todo.completed ? 'line-through' : 'none'
                        }}>
                            {todo.title}
                        </span>
                        <IconButton onClick={() => handleDelete(todo.id)}>
                            <DeleteIcon />
                        </IconButton>
                    </ListItem>
                ))}
            </List>
        </Container>
    );
}

Laravel + Inertia.js + React

resources/js/Pages/Todos/Index.jsx

import React from 'react';
import { useForm, router } from '@inertiajs/react';
import {
    Container,
    Typography,
    TextField,
    Button,
    List,
    ListItem,
    Checkbox,
    IconButton,
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';

export default function Index({ todos, errors }) {
    const { data, setData, post, processing, reset } = useForm({
        title: '',
    });

    // ToDo作成
    const handleSubmit = (e) => {
        e.preventDefault();
        post('/todos', {
            preserveScroll: true,
            onSuccess: () => reset('title'),
        });
    };

    // 完了状態をトグル
    const handleToggleComplete = (todo) => {
        router.put(`/todos/${todo.id}`, {
            completed: !todo.completed,
        }, {
            preserveScroll: true,
        });
    };

    // 削除
    const handleDelete = (todoId) => {
        if (window.confirm('このToDoを削除しますか?')) {
            router.delete(`/todos/${todoId}`, {
                preserveScroll: true,
            });
        }
    };

    return (
        <Container maxWidth="md" sx={{ mt: 4 }}>
            <Typography variant="h3" component="h1" gutterBottom>
                ToDo アプリ
            </Typography>

            <form onSubmit={handleSubmit}>
                <TextField
                    fullWidth
                    label="新しいToDoを入力"
                    value={data.title}
                    onChange={(e) => setData('title', e.target.value)}
                    error={!!errors.title}
                    helperText={errors.title}
                    disabled={processing}
                />
                <Button
                    type="submit"
                    variant="contained"
                    disabled={processing || !data.title.trim()}
                >
                    追加
                </Button>
            </form>

            <List>
                {todos.map((todo) => (
                    <ListItem key={todo.id}>
                        <Checkbox
                            checked={todo.completed}
                            onChange={() => handleToggleComplete(todo)}
                        />
                        <span style={{
                            textDecoration: todo.completed ? 'line-through' : 'none'
                        }}>
                            {todo.title}
                        </span>
                        <IconButton onClick={() => handleDelete(todo.id)}>
                            <DeleteIcon />
                        </IconButton>
                    </ListItem>
                ))}
            </List>
        </Container>
    );
}

差分:

  • useStateuseEffectによるデータ管理が不要
  • ✅ API呼び出しコード(axios)が不要
  • ✅ ローディング状態の管理が自動化(processing
  • ✅ エラーハンドリングが簡素化
  • ✅ データの再取得が不要(自動的に更新される)
  • ✅ Propsとしてtodoserrorsを直接受け取れる
  • ✅ コード量が約50%削減

2.4 エントリーポイント

従来のLaravel + React SPA

Frontend: src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
);

Frontend: public/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Todo App</title>
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>
</html>

Laravel + Inertia.js + React

resources/js/app.jsx

import './bootstrap';
import '../css/app.css';
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

createInertiaApp({
    title: (title) => `${title} - ${appName}`,
    resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
    setup({ el, App, props }) {
        const root = createRoot(el);
        root.render(<App {...props} />);
    },
    progress: {
        color: '#4B5563',
    },
});

resources/views/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title inertia>{{ config('app.name', 'Laravel') }}</title>
    
    @routes
    @viteReactRefresh
    @vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
    @inertiaHead
</head>
<body>
    @inertia
</body>
</html>

差分:

  • @inertiaディレクティブでReactコンポーネントがマウントされる
  • @routesでLaravelのルートがJavaScriptで利用可能
  • ✅ Bladeテンプレートの機能(多言語対応、設定値など)が使える
  • ✅ ViteとLaravelの統合が簡単

3. データフローの違い

従来のLaravel + React SPA

┌─────────────┐         ┌─────────────┐         ┌──────────────┐
│   Browser   │  HTTP   │   Laravel   │  Query  │  PostgreSQL  │
│   (React)   │ ──────> │   API       │ ──────> │              │
│             │ <────── │             │ <────── │              │
└─────────────┘  JSON   └─────────────┘         └──────────────┘
      │                                                  
      │ 1. axios.get('/api/todos')                      
      │ 2. Response: { data: [...], message: 'Success' }
      │ 3. setTodos(response.data.data)                 
      │ 4. Re-render component                          
      └─────────────────────────────────────────────────

フロー:

  1. React側でaxiosを使ってAPI呼び出し
  2. LaravelがJSONレスポンスを返す
  3. JavaScript側でステート更新
  4. 手動で再レンダリング

Laravel + Inertia.js + React

┌─────────────┐         ┌─────────────┐         ┌──────────────┐
│   Browser   │  HTTP   │   Laravel   │  Query  │  PostgreSQL  │
│   (React)   │ ──────> │   (Inertia) │ ──────> │              │
│             │ <────── │             │ <────── │              │
└─────────────┘  HTML+  └─────────────┘         └──────────────┘
      │          JSON                                    
      │ 1. router.put('/todos/1', {...})                
      │ 2. Inertia sends data as XHR                    
      │ 3. Laravel validates & updates DB               
      │ 4. return back()                                
      │ 5. Inertia auto-reloads props                   
      │ 6. Component re-renders with new data           
      └─────────────────────────────────────────────────

フロー:

  1. Inertia.jsのルーター経由でリクエスト
  2. Laravelが処理してリダイレクト
  3. Inertia.jsが自動的にデータを再取得
  4. Reactコンポーネントが自動的に再レンダリング

差分:

  • ✅ 手動でのデータ再取得が不要
  • ✅ ステート管理が不要(Inertiaが管理)
  • ✅ JSONレスポンスの構築が不要

4. パッケージ依存関係の比較

従来のLaravel + React SPA

Backend: composer.json

{
    "require": {
        "laravel/framework": "^11.0",
        "laravel/sanctum": "^4.0",
        "fruitcake/laravel-cors": "^3.0"
    }
}

Frontend: package.json

{
    "dependencies": {
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-router-dom": "^6.20.0",
        "axios": "^1.6.0",
        "@mui/material": "^5.15.0",
        "@mui/icons-material": "^5.15.0",
        "@emotion/react": "^11.11.0",
        "@emotion/styled": "^11.11.0"
    }
}

合計: 約10パッケージ


Laravel + Inertia.js + React

composer.json

{
    "require": {
        "laravel/framework": "^11.0",
        "inertiajs/inertia-laravel": "^1.0",
        "tightenco/ziggy": "^2.0"
    }
}

package.json

{
    "dependencies": {
        "@inertiajs/react": "^1.0.0",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "@mui/material": "^5.15.0",
        "@mui/icons-material": "^5.15.0",
        "@emotion/react": "^11.11.0",
        "@emotion/styled": "^11.11.0"
    },
    "devDependencies": {
        "@vitejs/plugin-react": "^4.2.1",
        "laravel-vite-plugin": "^1.0.0",
        "vite": "^5.0.0"
    }
}

合計: 約10パッケージ(axiosやreact-router-domが不要)

差分:

  • ❌ 不要: laravel/sanctum, fruitcake/laravel-cors
  • ❌ 不要: axios, react-router-dom
  • ✅ 追加: @inertiajs/react, inertiajs/inertia-laravel

5. Inertia.jsのメリットまとめ

5.1 開発効率の向上

項目 従来のSPA Inertia.js 改善率
セットアップ時間 約2時間 約30分 75%削減
コード行数(コントローラー) 約80行 約50行 38%削減
コード行数(フロントエンド) 約120行 約60行 50%削減
設定ファイル数 約8ファイル 約4ファイル 50%削減

5.2 主要なメリット

✅ 1. プロジェクト構造のシンプル化

  • 1つのリポジトリで管理
  • フロントエンドとバックエンドの分離が不要
  • デプロイが簡単(1つのアプリケーション)

✅ 2. API層の削減

  • JSONレスポンスの構築が不要
  • API仕様書の管理が不要
  • REST APIの設計・実装コストがゼロ

✅ 3. 認証・認可の簡素化

  • Laravelのセッション認証をそのまま使用可能
  • CORS設定が不要
  • Sanctumやトークン管理が不要

✅ 4. データ管理の自動化

  • フロントエンド側の状態管理が不要
  • データの再取得が自動化
  • ローディング状態の管理が簡単

✅ 5. 型安全性の向上(TypeScript併用時)

  • Laravelのモデルから型定義を自動生成可能
  • バックエンドとフロントエンドの型整合性が保たれる

✅ 6. SEO対応が容易

  • サーバーサイドレンダリング(SSR)が簡単に追加可能
  • メタタグの管理がBlade側で可能

✅ 7. 学習コストの低減

  • React Router、Axios、状態管理ライブラリの学習が不要
  • Laravelの知識だけで開発可能

5.3 デメリット・制約

⚠️ 1. モバイルアプリとの共有が難しい

  • REST APIがないため、ネイティブアプリ用に別途APIが必要

⚠️ 2. マイクロサービスに不向き

  • フロントエンドとバックエンドが密結合
  • 独立したサービス分割が難しい

⚠️ 3. 大規模チームでの分業

  • フロントエンドとバックエンドの完全分離ができない
  • 役割分担が曖昧になる可能性

6. どちらを選ぶべきか?

Inertia.jsが適しているケース

Webアプリケーション専用のプロジェクト
小〜中規模のチーム(1-10人)
迅速な開発が求められる
✅ Laravelの機能(認証、バリデーション等)をフル活用したい
✅ フロントエンドとバックエンドを同じチームが開発

従来のSPAが適しているケース

モバイルアプリも展開する予定
大規模チームでフロントエンドとバックエンドを完全分離
マイクロサービスアーキテクチャを採用
✅ 複数のクライアント(Web、iOS、Android)で同じAPIを使用


7. 実際のプロジェクト例

本記事で比較したToDoアプリケーションは、以下の構成で実装されています。

ディレクトリ構造(Inertia.js版)

inertiajs_tutorial_todo/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── Controller.php
│   │   │   ├── HealthController.php
│   │   │   └── TodoController.php       # シンプルなコントローラー
│   │   └── Middleware/
│   │       └── HandleInertiaRequests.php # エラー共有など
│   └── Models/
│       └── Todo.php
├── resources/
│   ├── js/
│   │   ├── Pages/
│   │   │   └── Todos/
│   │   │       └── Index.jsx            # メインコンポーネント
│   │   └── app.jsx                      # Inertiaエントリーポイント
│   └── views/
│       └── app.blade.php                # ルートテンプレート
├── routes/
│   └── web.php                          # 全ルートを定義
├── docker-compose.yml
├── Dockerfile
└── vite.config.js

起動方法

# Docker環境の起動
docker compose up -d --build

# 依存関係のインストール
docker compose exec app composer install
docker compose exec app npm install

# マイグレーション
docker compose exec app php artisan migrate

# ビルド
docker compose exec app npm run build

# アクセス
# http://localhost:8080

まとめ

Inertia.jsは、従来のSPAアーキテクチャの複雑さを大幅に削減しながら、モダンなReact開発体験を実現する革新的なフレームワークです。

コード量を約40-50%削減し、設定ファイルを半分にできるだけでなく、Laravelの強力な機能(認証、バリデーション、ORM等)をそのまま活用できる点が最大の魅力です。

ただし、モバイルアプリとの併用やマイクロサービス化を検討している場合は、従来のREST API方式を選択する必要があります。

プロジェクトの要件に応じて適切なアーキテクチャを選択し、効率的な開発を実現しましょう!


参考リンク


この記事が役に立ったら、ぜひシェアしてください! 🚀

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?