| この記事の要点 |
|
連携パターンの選択
4パターンは別の構成です。すべてのコードを1プロジェクトへ順番に追加する手順ではありません。まず採用する方式とcomposer.lock・package-lock.jsonの版を確認します。旧Vue 2 / Mixの構成とVue 3 / Viteの構成は混ぜません。
| パターン | 特徴 | 向いている用途 |
|---|---|---|
| ① Blade + Vue 部分導入 | Bladeのマウント領域へVueのSFCを描画 | 既存 Laravel への段階導入 |
| ② Inertia.js | Vue でビュー、Laravel ルーティング維持 | SPA UX が欲しいが認証は Laravel |
| ③ API + Vue SPA 分離 | Laravel = JSON API のみ、Vue = 別プロジェクト | モバイル併用 / フロント独立 |
| ④ Nuxt + Laravel API | Nuxt で SSR、Laravel で API | SEO 重要 / SSR 必要 |
パターン1: Blade + Vue 部分導入 (Vite)
Viteを採用するLaravelプロジェクトの例です。旧9.x等の既存環境は実際のビルド設定を確認してください。vite.config.js + resources/js/app.js で Vue 3 をマウントします。
# Laravel 新規プロジェクト
composer create-project laravel/laravel my-app
cd my-app
# Vue 3 と Vite プラグインを追加
npm install vue@3 @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({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});import './bootstrap';
import { createApp } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
const root = document.querySelector('#app');
if (root) {
const user = JSON.parse(root.dataset.user);
createApp(HelloWorld, { user, message: root.dataset.message }).mount(root);
}{{-- resources/views/welcome.blade.php --}}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<div id="app"
data-user="{{ json_encode($user, JSON_THROW_ON_ERROR) }}"
data-message="こんにちは"></div>
</body>
</html>resources/js/components/HelloWorld.vue
<script setup>
defineProps({
user: { type: Object, default: null },
message: { type: String, default: '' },
});
</script>
<template>
<div>
<template v-if="user">
<h1>{{ message }}, {{ user.name }} さん</h1>
<p>email: {{ user.email }}</p>
</template>
<p v-else>ユーザー情報はありません。</p>
</div>
</template># 開発サーバ起動 (HMR 付き)
npm run dev
# 本番ビルド
npm run build
# → public/build/ に出力
Bladeの変数と描画方式を確認する
Viteの通常構成は実行時コンパイラを含みません。createApp({})でBlade内のhello-worldタグをテンプレートとして読ませる代わりに、ビルド済みSFCをルートコンポーネントとして直接マウントします。上のdata-userはBladeが属性値をエスケープし、JavaScript側でJSON.parseしてpropsへ渡します。
この例の$userはEloquentモデル全体ではなく、表示用のname・emailだけを含む配列またはnullです。例えばルートで次のように用意します。サンプル文字列を実ユーザー情報へ変更するときも、ログイン本人に見せてよい項目だけに限定してください。
// routes/web.php: 公開してよいダミーデータの例
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome', ['user' => [
'name' => 'Sample User',
'email' => 'sample@example.test',
]]);
});ユーザーなしの表示はuserをnullにして確認できます。ブラウザのConsoleにtemplate compilerに関する警告が出る場合、SFCのimport先・Viteプラグイン・実際に読まれたビルドを確認します。
パターン2: Inertia.js (SPA 風)
画面のビューをVueで書き、初期HTMLを返すルートBladeを残しながら、ルーティング / 認証 / バリデーションは Laravel に任せる。Jetstream / Breeze のオプションで導入できます。
次のBreezeコマンドはBreezeが対応する既存Laravel構成の例です。最新Laravelへ無条件に導入する手順ではありません。新規構成は対象版の公式Starter Kitsを確認してください。
# Breeze + Inertia + Vue
composer require laravel/breeze --dev
php artisan breeze:install vue
npm install
npm run dev// routes/web.php
use Inertia\Inertia;
use App\Models\Post;
Route::get('/posts', function () {
return Inertia::render('Posts/Index', [
'posts' => Post::latest()->orderByDesc('id')->select(['id', 'title'])->get(),
]);
});
// または Controller
public function index() {
return Inertia::render('Posts/Index', [
'posts' => Post::latest()->orderByDesc('id')->select(['id', 'title'])->paginate(20),
'filters' => request()->only(['search']),
]);
}以下はget()で取得した配列を受け取るIndex.vueです。上のControllerのpaginate(20)を採用する場合は、この例ではなく後のページネーション版を使用します。
<!-- resources/js/Pages/Posts/Index.vue -->
<script setup>
import { Link } from '@inertiajs/vue3';
defineProps({
posts: Array,
});
</script>
<template>
<h1>記事一覧</h1>
<ul>
<li v-for="post in posts" :key="post.id">
<Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
</li>
</ul>
</template>paginate()を使う場合のVueコンポーネント
paginate()のJSONは配列ではなく、dataとページ情報を持つオブジェクトです。Controller例を選んだ場合はIndex.vueを次の内容にします。前後のURLがnullならリンクを出しません。記事詳細の/posts/{id}ルートは別途実装が必要です。
<script setup>
import { Link } from '@inertiajs/vue3';
defineProps({ posts: { type: Object, required: true } });
</script>
<template>
<h1>記事一覧</h1>
<ul>
<li v-for="post in posts.data" :key="post.id">
<Link :href="`/posts/${post.id}`">{{ post.title }}</Link>
</li>
</ul>
<nav aria-label="ページ切替">
<Link v-if="posts.prev_page_url" :href="posts.prev_page_url">前へ</Link>
<span> {{ posts.current_page }} / {{ posts.last_page }} </span>
<Link v-if="posts.next_page_url" :href="posts.next_page_url">次へ</Link>
</nav>
</template>
パターン3: API + Vue SPA 完全分離
Laravel をバックエンド API のみとして使い、Vue は別ディレクトリ / 別サーバで動かします。認証は Laravel Sanctum が定番。
# Laravel 側
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# .env
SANCTUM_STATEFUL_DOMAINS=app.example.test:5173
SESSION_DOMAIN=.example.test
# Vue 側 (別プロジェクト)
npm create vue@latest my-spa
cd my-spa
npm install axios pinia vue-router// routes/api.php
use App\Http\Controllers\Api\PostController;
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
Route::get('/user', fn() => auth()->user());
});// src/api.js (Vue 側)
import axios from 'axios';
const api = axios.create({
baseURL: 'http://api.example.test',
withCredentials: true, // Sanctumクッキー認証
withXSRFToken: true, // 別オリジンへCSRFヘッダを送る
headers: { 'Accept': 'application/json' },
});
// ログイン
export async function login(email, password) {
// 1. CSRF クッキー取得
await api.get('/sanctum/csrf-cookie');
// 2. ログイン
await api.post('/login', { email, password });
}
// 記事一覧取得
export async function fetchPosts() {
const { data } = await api.get('/api/posts');
return data;
}
Cookie認証が成立する条件
上の例はフロントがhttp://app.example.test:5173、APIがhttp://api.example.testの開発環境です。名前解決とViteのhost・allowedHostsを用意してください。localhostとexample.testを混ぜず、SanctumのSPA Cookie認証では同じ親ドメインを使います。本番はHTTPSと実際のドメイン設定に合わせます。
現行Laravelの導入は対象版のinstall:apiとbootstrap/app.phpのstatefulApi設定を確認します。上のvendor:publish等は既存構成の例です。DBに触れる導入操作は学習用の別環境だけで行い、このサイトの本番DBへ実行しないでください。
CORSでは許可元をフロントの実オリジンに限定し、supports_credentialsを有効にします。APIだけでなくsanctum/csrf-cookieとloginも必要な対象です。Cookieの共有範囲、CSRFヘッダ、セッション認証の/login実装、APIの権限検査が必要で、Sanctumを追加しただけでログインやPostControllerが完成するわけではありません。
| 症状 | 確認する場所 |
|---|---|
| CORSエラー | 許可元オリジン・対象パス・資格情報 |
| 419 | CSRF Cookie取得後、X-XSRF-TOKENヘッダとCookieが送られたか |
| 401 | ログイン成功・セッションCookie・stateful対象 |
| 403 | ログインとは別の閲覧・更新権限 |
パターン4: Nuxt 3 + Laravel API (SSR)
以下はNuxt 3構成の参考です。公式ではNuxt 3は2026年7月31日にサポート終了しています。新規開発はサポート対象版を選び、ディレクトリ等も対象版の資料へ合わせてください。latestだけではNuxt 3を指定したことにならないため、この旧構成例ではv3テンプレートを明示しています。
# Nuxt 3 プロジェクト作成
npm create nuxt@latest my-nuxt -- -t v3
cd my-nuxt
npm install
npm run dev # http://localhost:3000
# .env
NUXT_PUBLIC_API_BASE=http://api.example.testuseRuntimeConfig()でapiBaseを読むには、環境変数だけでなくnuxt.config.tsに公開キーを定義します。public内には秘密情報を置きません。
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: { apiBase: 'http://api.example.test' },
},
});<!-- pages/posts/index.vue -->
<script setup>
const config = useRuntimeConfig();
// SSR でサーバ側 fetch
const { data: posts } = await useFetch(`${config.public.apiBase}/api/posts`);
</script>
<template>
<ul>
<li v-for="post in posts" :key="post.id">
<NuxtLink :to="`/posts/${post.id}`">{{ post.title }}</NuxtLink>
</li>
</ul>
</template>
Nuxtの一覧例はAPIがJSON配列を返す前提です。Laravelのpaginate()ならdata部分を取り出す処理へ変更します。SSRからのリクエストはブラウザのCookie認証と別で、外部APIへCookieが自動共有されるとは限りません。認証情報を無条件に外部APIへ転送せず、サーバー側の接続・認証方式を設計してください。
Laravel Mix から Vite への移行
古い Laravel (8.x 以前) は webpack.mix.js を使っていました。新規と移行は分けて扱います。移行前に変更をコミットし、Mixの入出力・CSS・画像参照・npmスクリプトを確認してください。次の削除操作は退避済みの学習用環境でのみ行い、Viteのビルド結果を確認してから置き換えます:
# Mix を削除
npm uninstall laravel-mix
rm webpack.mix.js
# Vite を導入
npm install -D vite laravel-vite-plugin @vitejs/plugin-vue
# vite.config.js を作成 (前述参照)
# Blade テンプレートを書き換え
# 旧: <link href="{{ mix('css/app.css') }}" rel="stylesheet">
# 新: @vite(['resources/css/app.css', 'resources/js/app.js'])
# 起動
npm run dev
FAQ
Q: Inertia と API + Vue SPA、どちらを選ぶ?
A: Laravel チームで Vue を書きたい → Inertia。フロントとバックを別チームで分けたい → API 分離。
Q: Vue から CSRF トークンを送る
A: SanctumはCSRF Cookie取得に加え、CookieとX-XSRF-TOKENヘッダの送信設定が必要です(上の条件を参照)。Blade 内 Vue は <meta name="csrf-token" content="{{ csrf_token() }}"> を axios のヘッダにセット。
Q: TypeScript 対応は?
A: Vite + Vue 3 で npm install -D typescript vue-tsc、tsconfig.json 配置。Inertia + TS も Breeze で選択可。
確認した公式資料
Vueの実行時コンパイラとSFC、LaravelのページネーションJSON、SanctumのSPA認証設定、Nuxt 3の対象版と導入、Nuxt runtimeConfig(2026年10月確認)。4方式の導入全体を実行保証するものではありません。
子ページはありません
- インストールと設定
- クイックスタート & チュートリアル(初心者向け)
- クイックスタート & チュートリアル(中級者向け)
- ルーティング
- Bladeテンプレート(ビュー/レイアウト)
- コントローラー
- マイグレーションとテーブル定義
- データベースの設定
- Eloquentモデル (ORM)
- SQLとクエリビルダー
- バリデーション
- .envファイルの設定値へのアクセス
- 動作環境による分岐処理
- configフォルダ配下の設定値へのアクセス
- assetヘルパーを利用したpublicフォルダへのアクセス
- storageフォルダへのアクセス
- アプリケーション名の変更
- メンテナンス
- ログイン画面(認証システム)の作成
- ログインの必須化
- ログインユーザー情報の取得
- ルートの認証化
- 本番サーバーへのデプロイ方法
- 多言語化
- csrf_field
- ファイルのダウンロード
- CSVのアップロードおよび読み込み(maatwebsite/excel)
- ページタイトルの設定
- コマンド一覧
- エラー一覧
- SQLの実行ログ出力方法
- キャッシュのクリア
- Selectの結果の最初もしくは最後に任意の値を追加する方法
- ajaxでPOST通信する際の注意点
- ソーシャルログインの実装
- セッション情報の確認
- ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
- redirectやreturn viewにメッセージを付与する方法
- クッキー(cookie)の設定と取得
- クラスの再読み込み
- csrfの有効時間を変更する方法
- ViewComposerを用いてviewに共通の値を付与する方法
- View::shareを用いて共通の値を各ビューに渡す方法
- ミドルウェアを用いた処理の共通化
- Middleware内でAuth::check()などを使用する方法
- Controller以外でリダイレクトする方法
- セッションの値の取得/保存/更新/削除
- $requestの値を変更する方法
- 常時SSL化
- ページング(ページネーション)をする方法
- vue.jsとの連携
- Vue.jsと連携するSPA実行環境構築
- .envの値をvue.jsで参照する方法
- vue.jsを本番環境にリリースする方法
- could not find driver(Windows, MySQL編)
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38