◀ 51.

Laravel + Vue.js 連携完全ガイド (Vite / Inertia / SPA)

▶
この記事の要点
  • Laravel + Vue 連携パターン 4 つ: ① Blade 内で Vue マウント ② Inertia.js (SPA 風) ③ API + Vue SPA 分離 ④ Nuxt SSR + Laravel API
  • 現在のLaravel新規構成では Laravel Mix → Vite が公式デフォルト
  • BladeからVueへは表示に必要な項目だけをdata属性で渡し、SFCのpropsとして受け取る
  • Inertia.js は 画面はVue/Reactコンポーネント、ルーティングはLaravel(初期HTMLにはルートBladeを使用) のハイブリッド (Laravel Jetstream / Breeze で対応)
  • API 分離型は Sanctum (SPA 認証) + axios + Pinia が定番

連携パターンの選択

Bladeはprops、Inertiaはデータ形式、API分離はCookieとCSRFを確認するLaravel・Vue連携の説明図

4パターンは別の構成です。すべてのコードを1プロジェクトへ順番に追加する手順ではありません。まず採用する方式とcomposer.lock・package-lock.jsonの版を確認します。旧Vue 2 / Mixの構成とVue 3 / Viteの構成は混ぜません。

パターン特徴向いている用途
① Blade + Vue 部分導入Bladeのマウント領域へVueのSFCを描画既存 Laravel への段階導入
② Inertia.jsVue でビュー、Laravel ルーティング維持SPA UX が欲しいが認証は Laravel
③ API + Vue SPA 分離Laravel = JSON API のみ、Vue = 別プロジェクトモバイル併用 / フロント独立
④ Nuxt + Laravel APINuxt で SSR、Laravel で APISEO 重要 / 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エラー許可元オリジン・対象パス・資格情報
419CSRF 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.test

useRuntimeConfig()で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方式の導入全体を実行保証するものではありません。

Post Share
子ページ

子ページはありません

同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)