Laravelも13がリリースされ開発環境を新たに作成、LaravelはPHP8.3以上が必要です。
Laravelと連携する場合vue-routerが非推奨になり、Inertia.jsとの連携が推奨されています。
そしてこのInertiaを使用することによってDB連携が強化されaxiosによるデータ送出も不要となりますし、フォームバリデーション制御、ページャー制御、サブパス制御も簡潔になります。
何より処理が爆速となります。たとえばleafletで地図上(open street map)のマーカー情報をドラッガブルで取得し、その複数検索結果をリスト化して返すようなシステムを作ったとしても、レスポンスは平均してわずかに0.3秒程度です。
元々は別の記事(削除というよりか集約済)を最初に作り、それを応用したシステムと技術共有目的に作ったのが以下の記事でした。
今度は逆にそれを最新技術(2026年6月時点)にブラッシュアップしたのが今回の記事となります。
昨今、アメリカにおいて劇的にVueの採用率が上昇している理由として、元々シェアの高いLaravelをスムーズに連携させる、このInertiaの存在が非常に大きいといわれています。
なお、InertiaはReact、Svelteとの連携も可能ですが、スムーズな連携かつ最適なパフォーマンスを実現できるのがVueです(日本ではReactでの連携記事が多いですが、Vueでの記事、そして世界が実際導入している最新のやり方※を記述した記事が少ないと感じたので、ブラッシュアップした記事を再編集したものです)。
※他の記事を見てもらえればわかると思いますが、自分はGemini信奉者なので、それに質問、協働しながら情報とデータをブラッシュアップしています。また、記事に使ったデータはテスト用(非公開)ですが、バグなく起動しているものです。
ひとまずVue3.5とLaravel13を導入
Laravel13とVite、そしてVue3.5の導入はLaravel公式のbreezeを使うと簡単に設定できます(自動的にLaravel、Vue、Inertia.jsが用意される)。
ところがローカルサーバでシステムを構築する場合npm、php artisanを順番に起動していっても、仮想マシンの場合Vueが読み込んでくれません。かといって無理にホストやポートを合わせてみても、server invalidなどと表示され、うまく接続されません。
解決方法
どうやらHMRを設定するといいようです。
Windows - Failed to load when loading vite dev server resources
この賞賛の多い回答にしたがってvite.config.jsを以下のように追記します。ついでにcssも設定しておきます。
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', //ここでbootstrapやtailwindを呼び出す
'resources/js/app.js',
],
refresh: true,
}),
vue(), //vueの使用
],
server: { // plugins の外側(同列)に配置
host: '0.0.0.0',
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
},
hmr: {
host: '192.168.11.xx', //任意のIPアドレス
},
}
});
これでjsフレームワークは起動ホストとポートにしたがって起動してくれます。ですが、これで終わりではありません。今度はviteに対するホストとポートの設定、それからLaravelに対するホストとポートの設定も必要です。
vite側のホスト・ポート設定
vite側のホスト、ポート設定ですが、HMRを使用したことで、ホストは明示的に記述しないと、Laravelはそのパスのまま読み込もうとするので、packege.jsonにホスト情報を追記しておきます。
"scripts": {
"dev": "vite --host 0.0.0.0", //ここにホスト情報を追記する
"build": "vite build"
},
Vue側のサーバ
#npm run dev
なお、デフォルトポートは5173ですが、これはそのままにしておいて大丈夫です。
Laravel側のホスト設定
Laravel側はphp artisanコマンドで制御します。
#php artisan serve --host 0.0.0.0
これでポートを8000とした場合にルートアドレスでVueが起動すれば成功です。失敗している場合は開発コンソールで、jsの設定部分を見直してください。
VueとLaravelを同時に起動する
片方でVueを立ち上げ、もう片方でLaravelを立ち上げます。ポートはLaravel基準(8000番、あるいはVite.config.jsに記述したポート番号)となるので、VueはLaravelと重複さえしていなければ番号は問いません(デフォルトの5173で十分)。
some_projectは任意のプロジェクト名
#some_project> npm run dev
#some_project> php artisan serve --host 0.0.0.0
順番はどちらが先でも問題ありません。また、編集の際に、vue側のサーバは起動したまま放置しても問題ありません。
InertiaによるVueファイル起動
今回の肝となる部分です。以下の仕組みからresources/js/Pages直下に作ったVueコンポーネントを制御していきます(ここにプロジェクト用のフォルダを作って、中にコンポーネントを設定します)。
import { createApp, h } from "vue";
import {createInertiaApp } from '@inertiajs/vue3';
createInertiaApp({
resolve: name =>{
//ここが一番のポイントで、Pagesフォルダ直下にコンポーネントを置かないといけない仕様
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-routerとは大きく異なり、Vueの標準仕様であるslotコンポーネントを活用します(これが、爆速システムを実現できる理由の一つ)。
レイアウト画面
<template>
<!-- コンテンツ全体をTopLayoutでラップ -->
<TopLayout>
<div class="content-body">
</div>
</TopLayout>
</template>
<script setup>
// 外枠となる Top.vue をインポートして適用
import TopLayout from './Top.vue'
</script>
トップコンポーネント画面(ここのslotがルータのoutletのように切り替わる)
<template>
<HeaderComponent />
<main><slot /></main>
</template>
<script setup>
import HeaderComponent from './HeaderComponent.vue'; //ヘッダー
</script>
ルーティングファイルについて
具体的な画面切換処置を行うのはVueコンポーネントの方なので、ルーティング設定はroutes/web.phpで行います。
またInertiaの強みであるDBとの連携もここで実行でき、Teamというモデルからクエリを取得したりしています(自作別記事のaxios使用システムを書き換えたものです)。また、vue-routerの代わりにもなり、ページャー制御までInertia一つで簡単に制御可能です。
<?php
use Illuminate\Support\Facades\Route;
use App\Models\Team; //DBモデル
use Inertia\Inertia;
use Illuminate\Http\Request;
// トップコンポーネント
Route::get('/', function(){
return Inertia::render('Teams/DashBoard');
});
//一覧(kindはサブパス対応)
Route::get('/teams/{kind?}', function($kind = null){
$query = Team::query(); //クエリを呼び出す
if($kind){
$query->where('kind',$kind); //where条件
$per_page = 10; //サブパスは10件ずつ
}else{
$per_page = $query -> count(); //全検索はページャー区切りなし
if($per_page == 0) $per_page = 1; //最低限1件
}
$pagenated=$query->paginate($per_page);
$teams = $pagenated->through(function($team){
return[
'id' => $team -> id,
'kind' => $team -> kind,
'name' => $team -> name,
'content' => $team -> content,
'area' => $team -> area,
'place' => $team -> place,
'home' => $team -> home,
];
});
return Inertia::render('Teams/TeamListComponent',['teams'=> $teams]);
})-> where('kind','(NFL|MLB|NBA|NHL|MLS)?'); //正規表現ガードでサブパスに対応させる
//入力
Route::get('/create', function(){
$options = Team::distinct()->pluck('kind'); //スポーツ団体のみ取得
return Inertia::render('Teams/AddComponent',['struct' => new Team(),'options'=>$options]);
});
//登録
Route::post("/teams",function(Request $request){
//バリデーションのカスタマイズ
$request -> validate([
'id' => ['required','digits_between:1,3','unique:teams,id'],
'name' => ['required','string','max:255'],
'kind' => ['required'],
],[
'id.digits_between' => "idは最大3桁の数値を入力してください",
'id.unique' => "idが重複しています。他の数値を入力してください",
'kind.required' => "種類は必ずいずれかを選択してください",
]);
Team::create($request->all());
return redirect('/');
});
//編集
Route::get('/edit/{id}', function($id){
$teamDetail = Team::findOrFail($id); //該当の値を取得(findOrFailは主キーから取得)
return Inertia::render('Teams/EditComponent',['team'=> $teamDetail]);
});
//更新
Route::put('/teams/{id}',function(Request $request, $id){
$request -> validate([
'id' => ['required','digits_between:1,3','unique:teams,id'],
'name' => ['required','string','max:255'],
'kind' => ['required'],
],[
'id.digits_between' => "idは最大3桁の数値を入力してください",
'id.unique' => "idが重複しています。他の数値を入力してください",
'kind.required' => "種類は必ずいずれかを選択してください",
]);
$team = Team::findOrFail($id); //対象データの取得
$team->update([
'name' => $request -> name,
'content' => $request -> content,
'area' => $request -> area,
'place' => $request -> place,
'home' => $request -> home,
]);
return redirect()->back();
});
//詳細
Route::get('/detail/{id}', function($id){
$teamDetail = Team::findOrFail($id); //該当の値を取得
return Inertia::render('Teams/DetailComponent',['team'=> $teamDetail]);
});
//削除
Route::delete('/teams/{id}',function($id){
$team = Team::findOrFail($id);
$team -> delete();
return redirect('/');
});
DBモデルについて
DBモデルはInertiaとデータ連携させるために必須です。逆にそれまで必要としていたDB用のコントローラーは不要となります(Inertiaが全部中継してくれる)。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Team extends Model
{
use HasFactory;
public $timestamps = false;
protected $fillable = [
'id',
'kind',
'name',
'content',
'area',
'place',
'home',
];
}
インラインテンプレートの一例
実装してみたチームリストページを一例で載せてみます。
肝となるのはInertiaの機能であるusePageメソッドで、ここで呼び出したインスタンスからDBテーブル情報を取得したり(ルーティングファイルに設定されていたもの)、ページング情報を取得できたりします。
<script setup>
import Nav from './Nav.vue';
import Map from './Map.vue';
import TeamItem from './TeamItemComponent.vue';
import PageLink from './PageLink.vue';
import {Link} from '@inertiajs/vue3';
import {computed,watch,ref } from 'vue';
import { usePage } from '@inertiajs/vue3';
const flg = ref("all");
const pg = usePage(); //ページャー制御用のオブジェクト
const datum = computed(()=> pg.props.teams?.data||[]); //Inertiaを通して全データ取得
const c_page = computed(()=>pg.props.teams?.current_page|| 1); //ページ情報の取得
const lists = ref([]); //取得したDBテーブルor検索結果を格納
watch(datum,(nd)=>{
lists.value = [...nd]; //v-forディレクティブに同期させるための準備
},{ immediate: true, deep: true }) //immediate: true とすることですぐに発火可能
//地図から回収したマーカーのリスト化
const onreceive = (data)=>{
const markers = data.marker; //地図から回収したマーカー
const isDrag = data.isDrag; //ドラッグ処理完了のフラグ
if(isDrag){
if(markers && markers.length > 0){
lists.value = markers;
}else{
lists.value = [];
}
}else{
lists.value = [...datum.value];
}
}
</script>
<template>
<article>
<Nav />
<Map :datum="datum" :zoom="4" :kind="flg" @markers="onreceive" />
<div class="flex justify-center container">
<table class="table table-hover">
<thead class="thead-light">
<tr>
<th scope="col">#</th>
<th scope="col">種別</th>
<th scope="col">名称</th>
<th scope="col">場所</th>
<th scope="col">詳細</th>
<th scope="col">修正</th>
<th scope="col">削除</th>
</tr>
</thead>
<tbody>
<TeamItem
v-for="(team,idx) in lists"
:key="idx"
:team="team"
:page="c_page"
/>
</tbody>
</table>
</div>
<PageLink />
</article>
</template>
とにかくInertiaを使えば、vue-routerもLaravel用のルータapi.phpも細かいページャー切り分け制御もデータを取得するaxiosもprovideとinjectも全部不要になります。
例は、地図上でドラッグした領域に含まれるマーカーに対し、検索結果としてリストにリアルタイムで表示(レスポンスタイムは0.3秒未満)。
Inertia最高や!vue-routerなんか最初からいらんかったんや!
