Laravel 13のBladeにVue.jsを組み込む
作成日:2026.08.18
Laravel 13のBlade画面にVue.jsを部分的に組み込み、Controllerから渡した商品データを使って商品名を検索する方法を解説します。Viteの設定、BladeからVue.jsへの初期データの渡し方、Js::fromとdata-*属性の比較、npm run devによる確認方法、ブラウザ内検索とサーバー側検索の使い分けを扱います。
目次
Laravelで画面を作っていると、ページ全体はBladeで構成しつつ、検索欄や入力フォームなど一部だけをリアクティブにしたいことがあります。
今回は、既存のLaravelプロジェクトへVue.jsを組み込み、Bladeから渡した商品データを使って商品名を検索する画面を作ります。Laravel全体をSPAへ変更するのではなく、商品検索の部分だけをVue.jsで動かす構成です。
前提
今回の前提環境は以下の通りです。
- OS: Windows 11
- PHP: 8.3.15
- Composer
- Laravel Framework: 13.x
- Node.js: 20.20.2
- npm: 10.8.2
- ターミナル: PowerShell
Laravelのプロジェクトは、仮にsample-projectという名前のプロジェクトを使います。Laravelのプロジェクト作成やBladeで画面を表示するまでの流れは、Windows 11でLaravel 13を始める:環境構築からルーティング・Controller・Bladeまでを確認してください。
Vue.jsやViteなどのバージョンは、インストール時期やプロジェクトの依存関係によって変わります。記事の手順を試す場合は、プロジェクトのpackage-lock.jsonと、次のコマンドの結果も確認してください。
php --version
composer --version
php artisan --version
node --version
npm --version
Laravel 13.xでViteを使うには、Node.jsとnpmなどのフロントエンド環境が必要です。Vue.jsを使う場合は、LaravelのViteプラグインに加えて、Vue用の@vitejs/plugin-vueも使用します。詳しくはLaravel 13.xのVite公式ドキュメントを参照してください。
BladeとVue.jsを組み合わせる
LaravelとVue.jsを組み合わせる方法には、アプリケーション全体をVue.jsやInertiaで構成する方法もあります。
ただし、既存のLaravelアプリケーションへVue.jsを導入する場合、最初から画面全体の構成を変更する必要はありません。Bladeでページ全体を表示し、その中にVue.jsをマウントする要素を用意すれば、ページの一部だけをVue.jsで動かせます。
今回の処理の流れは、次のようになります。
ブラウザ
↓ /productsへアクセス
LaravelのRoute
↓
ProductController
↓ 商品データをBladeへ渡す
Blade
├─ ページ全体のHTMLを表示
├─ 初期データをJavaScriptへ渡す
└─ Vue.jsのマウント先を用意
↓
Vue.jsの検索コンポーネント
└─ 商品名で表示結果を絞り込む
Vue公式ドキュメントでも、createAppでアプリケーションを作成し、mountで指定したDOM要素へ表示する流れが説明されています。今回はこの仕組みをBladeの一部分に対して使います。
Vue.jsをLaravelプロジェクトへ追加する
パッケージをインストールする
まず、PowerShellでLaravelプロジェクトのルートディレクトリへ移動します。
cd D:\path\to\sample-project
Vue.jsとVue用のViteプラグインをインストールします。
npm install vue
npm install --save-dev @vitejs/plugin-vue
Laravelの新しいプロジェクトには、ViteとLaravel用プラグインの基本構成が用意されています。今回のようにVueのSingle-File Component(SFC)を使う場合は、@vitejs/plugin-vueを追加します。
vite.config.jsを設定する
プロジェクトルートのvite.config.jsを開き、Laravel用プラグインとVue用プラグインを設定します。
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
laravel(['resources/js/app.js']),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});
laravelには、Laravelから読み込むエントリーポイントとしてresources/js/app.jsを指定します。vueは、.vueファイルをViteで扱うためのプラグインです。
transformAssetUrlsは、Single-File Component内の画像などのアセットURLを扱うための設定です。今回の検索コンポーネントでは画像を使いませんが、Laravel 13.xの公式例に合わせて設定しています。
商品データを用意する
Controllerを作成する
商品一覧を表示するControllerを作成します。
php artisan make:controller ProductController
作成されたapp/Http/Controllers/ProductController.phpを、次の内容へ変更します。
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
class ProductController extends Controller
{
public function index(): View
{
$products = [
[
'id' => 1,
'name' => '脚付き将棋盤',
'price' => 128000,
'stock' => 5,
],
[
'id' => 2,
'name' => '折り畳み将棋盤',
'price' => 3980,
'stock' => 18,
],
[
'id' => 3,
'name' => '将棋駒',
'price' => 8800,
'stock' => 0,
],
];
return view('products.index', compact('products'));
}
}
今回はデータベースを使わず、Controller内に説明用の商品データを配列で用意しています。実際のアプリケーションでは、Modelやサービスから取得したデータをBladeへ渡す構成になるかと思います。
Vue.jsへ渡すデータには、商品名、金額、在庫数だけを含めています。ブラウザへ出力したデータは利用者が確認できるため、パスワード、内部用のメモ、権限判定に使う情報などを含めないようにします。
ルートを追加する
routes/web.phpに、Controllerを呼び出すルートを追加します。
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::get('/products', [ProductController::class, 'index']);
これで、/productsへアクセスするとProductControllerのindexメソッドが呼び出されます。
BladeからVueへ初期データを渡す
Js::fromを使う方法
次に、Bladeの画面を作成します。まず、ビューのディレクトリを作成します。
New-Item -ItemType Directory -Path .\resources\views\products
resources/views/products/index.blade.phpを作成し、次の内容を記述します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商品検索</title>
@vite('resources/js/app.js')
</head>
<body>
<main>
<h1>商品一覧</h1>
<p>商品名を入力すると、表示する商品を絞り込めます。</p>
<div id="product-search"></div>
</main>
<script>
window.productSearchProducts = {{ Js::from($products) }};
</script>
</body>
</html>
Js::from($products)は、PHPの配列をJavaScriptで扱える形式へ変換します。Laravel公式ドキュメントでは、json_encodeを直接呼び出す代わりに、HTMLへ埋め込むJSONを適切にエスケープする方法としてJs::fromが紹介されています。
ここでは、グローバルなwindow.productSearchProductsへ初期データを設定しています。後ほどresources/js/app.jsからこの値を読み込み、Vueコンポーネントへpropsとして渡します。
Js::fromへ渡す値は、あらかじめControllerなどで用意した変数にします。Bladeの複雑な式をそのまま渡すのではなく、必要なデータをController側で整理してからビューへ渡す方が、処理の役割を分けやすくなります。
data-*属性を使う方法との比較
初期データをHTML要素のdata-*属性へ埋め込み、JavaScriptのdatasetから読み込む方法もあります。
<div
id="product-search"
data-products="{{ json_encode($products, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) }}"
></div>
この場合は、JavaScriptで属性値をJSONとして解析します。
const element = document.getElementById('product-search');
const products = JSON.parse(element?.dataset.products ?? '[]');
data-*属性の値は文字列として扱われるため、配列やオブジェクトを渡す場合はJSONへの変換と、JavaScript側でのJSON.parseが必要です。また、HTML属性へ埋め込む値のエスケープも考えなければなりません。
比較すると、次のようになります。
| 方法 | 特徴 | 注意点 |
|---|---|---|
Js::from |
Bladeの変数をJavaScriptの初期値として扱いやすい | script要素へ出力するデータの公開範囲を確認する |
data-* |
特定のDOM要素に紐付けて値を渡せる | 値は文字列になるため、配列などはJSON.parseが必要 |
今回は配列を初期データとして渡すため、Js::fromを使います。商品検索のように単純な文字列や数値だけを渡す場合は、data-*属性も選択肢になります。
Vue.jsの検索コンポーネントを作る
エントリーポイントからコンポーネントを読み込む
次に、Vue.jsのコンポーネントを配置するディレクトリを作成します。
New-Item -ItemType Directory -Path .\resources\js\components
まず、resources/js/app.jsを次の内容にします。既存のimport './bootstrap';やCSSの読み込みがある場合は、残したまま追記してください。
import { createApp } from 'vue';
import ProductSearch from './components/ProductSearch.vue';
const element = document.getElementById('product-search');
if (element) {
createApp(ProductSearch, {
initialProducts: window.productSearchProducts ?? [],
}).mount(element);
}
document.getElementById()の結果を確認しているのは、Vueのマウント先が存在しないページでも、同じエントリーポイントを読み込めるようにするためです。
createApp(ProductSearch, {...})の第2引数に渡した値は、ルートコンポーネントのpropsになります。ここでは、Bladeから渡した商品配列をinitialProductsという名前で渡しています。
検索コンポーネントを実装する
resources/js/components/ProductSearch.vueを作成し、次の内容を記述します。
<script setup>
import { computed, ref } from 'vue';
const props = defineProps({
initialProducts: {
type: Array,
default: () => [],
},
});
const keyword = ref('');
const filteredProducts = computed(() => {
const normalizedKeyword = keyword.value.trim().toLowerCase();
if (normalizedKeyword === '') {
return props.initialProducts;
}
return props.initialProducts.filter((product) => {
return product.name.toLowerCase().includes(normalizedKeyword);
});
});
</script>
<template>
<section>
<h2>商品検索</h2>
<label for="product-keyword">商品名</label>
<input
id="product-keyword"
v-model="keyword"
type="search"
placeholder="商品名を入力"
>
<p v-if="filteredProducts.length === 0">
該当する商品はありません。
</p>
<ul v-else>
<li v-for="product in filteredProducts" :key="product.id">
<strong>{{ product.name }}</strong>
- {{ product.price.toLocaleString() }}円
(在庫: {{ product.stock }})
</li>
</ul>
</section>
</template>
keywordは検索欄へ入力された文字列を保持します。filteredProductsは、検索語に応じて商品配列を絞り込む計算結果です。
computedは、リアクティブな値から別の値を計算するときに使います。今回の処理では、keywordが変化したときにだけ検索結果が再計算されます。検索処理のように、元データから表示用の配列を作る処理に向いています。
検索語が空の場合は、初期データをそのまま表示します。検索語が入力された場合は、商品名を小文字化して部分一致で比較しています。今回は仕組みを確認するための例なので、全角・半角の変換や高度な検索には対応していません。
また、商品名や価格は、Vueの通常のテキスト表示で出力しています。商品データにHTMLを含めてv-htmlで表示する必要はありません。ユーザー入力などをHTMLとして表示する場合は、別途サニタイズを含む安全性の検討が必要です。
検索画面を表示する
Laravelの開発サーバーを起動する
PowerShellを2つ起動し、一方でLaravelの開発サーバーを起動します。
php artisan serve
通常は、http://127.0.0.1:8000でアクセスできます。
Viteの開発サーバーを起動する
もう一方のPowerShellで、Viteの開発サーバーを起動します。
npm run dev
ブラウザでhttp://127.0.0.1:8000/productsを開きます。「商品名を入力」欄へ文字を入力し、商品一覧が絞り込まれることを確認します。
npm run devは、Viteの開発サーバーだけを起動するコマンドです。そのため、今回は別のPowerShellでphp artisan serveも起動しています。
Laravelの新規プロジェクトでは、composer.jsonに複数の開発用プロセスをまとめて起動するcomposer run devが用意されている場合があります。プロジェクトによって実行内容が異なるため、利用する場合はcomposer.jsonのscriptsを確認してください。
composer run dev
単純にViteだけを確認したい場合はnpm run dev、Laravelの開発サーバーなどもまとめて起動したい場合はプロジェクトのcomposer run dev、というように使い分けると分かりやすいかと思います。
本番用にビルドする
開発サーバーでの確認が終わったら、本番用のアセットをビルドします。
npm run build
ビルドが成功すると、Laravelのpublic/buildにJavaScriptやCSSなどのアセットが作成されます。Bladeの@vite('resources/js/app.js')は、開発サーバーが起動している場合は開発用アセットを読み込み、ビルド後は生成されたアセットを読み込みます。
ビルド後にLaravelの開発サーバーを起動してhttp://127.0.0.1:8000/productsへアクセスし、検索画面が表示されることを確認します。
Bladeの変更を自動反映する場合
Vue.jsのファイルを変更した場合は、Viteの開発サーバーによって変更が反映されます。一方、Bladeの変更も保存時にブラウザへ反映したい場合は、vite.config.jsのLaravelプラグインへrefreshを設定できます。
laravel({
input: ['resources/js/app.js'],
refresh: true,
}),
refresh: trueを設定すると、Laravelのビューなどの変更を検知して、ブラウザを再読み込みできます。既存のVite設定へ追加する場合は、Laravelプラグインの配列形式をオブジェクト形式へ変更します。
ただし、今回の検索結果はページ表示時にBladeから渡された初期データを使っています。データベースの内容が変わった場合に自動で最新化される仕組みではありません。
ブラウザ内検索とサーバー側検索の使い分け
今回の検索は、Controllerから渡した商品データをブラウザ上で絞り込んでいます。検索のたびにLaravelへリクエストを送る処理はありません。
この方法は、次のような場合に向いています。
- 検索対象が少ない
- ページ表示時点で検索に必要なデータが揃っている
- 検索結果に対する権限管理が必要ない
- 入力のたびにサーバーへ通信したくない
一方、商品数がある程度多い場合は、初期表示時にすべての商品をHTMLへ埋め込むと、ページサイズやブラウザの処理量が増えます。また、在庫などの最新状態を確認する必要がある場合、ブラウザへ渡したデータだけで検索すると古い情報になる可能性があります。
そのような場合は、検索語をLaravelへ送信し、ControllerやModelで検索する構成を検討します。検索結果のページネーション、利用者ごとの権限、データの最新性が必要な場合は、サーバー側で検索結果を制御する方が適しています。
今回の記事では、APIやページネーションの実装までは扱いません。まずはBladeとVue.jsを組み合わせる最小構成として、初期データの受け渡しとブラウザ内検索の流れを確認します。
うまく動かない場合
Vueの検索欄が表示されない
検索欄そのものが表示されない場合は、次の項目を確認します。
npm run devが終了していないか- Bladeに
@vite('resources/js/app.js')があるか - Vueのマウント先が
id="product-search"になっているか app.jsのコンポーネントimport先が正しいか- ブラウザの開発者ツールとViteを起動したPowerShellにエラーがないか
検索結果が常に空になる
初期データがVueへ渡っているかを確認します。ブラウザの開発者ツールで、window.productSearchProductsを入力して値を確認できます。
値がundefinedの場合は、BladeのJs::from($products)、Controllerからビューへ渡している変数名、JavaScriptで参照しているwindowのプロパティ名が一致しているか確認します。
商品名が存在しないデータや、商品名が文字列ではないデータを渡すと、toLowerCase()の呼び出しでエラーになる可能性があります。サンプルと異なるデータを使う場合は、Controllerから渡す配列の構造も確認します。
npm run buildで失敗する
ビルドエラーが発生した場合は、次の項目を確認します。
vueと@vitejs/plugin-vueがインストールされているかvite.config.jsのimport文とプラグイン設定に誤りがないかresources/js/app.jsとProductSearch.vueのパスが正しいか- Node.jsとnpmのバージョンが、使用する依存パッケージの条件を満たしているか
まとめ
今回は、Laravel 13.xのBlade画面へVue.jsを組み込み、Controllerから渡した商品データを使って商品名を検索しました。
LaravelのVite設定へVue用プラグインを追加し、BladeにVueのマウント先を用意すると、ページ全体をSPAへ変更せずに一部分だけをVue.jsで動かせます。Bladeから初期データを渡す場合は、今回のような配列にはJs::fromを使う方法が分かりやすいかと思います。
今回の検索は、ページ表示時に取得したデータをブラウザ上で絞り込む構成です。検索対象が多い場合や、最新データ、ページネーション、権限管理が必要な場合は、Laravel側で検索する方法も検討します。
Laravelの画面をすべて作り直すのではなく、必要な部分からVue.jsを組み込む方法も選択肢の一つになります。
参考: Laravel 13.x Asset Bundling (Vite)、Laravel 13.x Blade Templates、Vue.js Creating a Vue Application、Vue.js Computed Properties
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
Laravel 13+Vue.jsで画像・CSVを非同期アップロードする:FormData・ファイル検証・非公開保存
2026.08.31
Laravel 13のBladeに組み込んだVue.jsから、FormDataを使って画像またはCSVを非同期で送信する方法を解説します。JPEG・PNGの画像検証、CSVの文字コード変換と簡易的な内容確認、20MBのサイズ制限、非公開ディスクへの保存、CSRF・バリデーションエラー処理を実装し、fetchを基本にaxiosとの違いも整理します。
Laravel 13+Vue.jsで登録・編集フォームを非同期化する:fetchとaxiosを比較
2026.08.26
Laravel 13のBlade画面にVue.jsを組み込み、membersテーブルのnameとemailを登録・編集するフォームを作ります。Vue.jsからLaravelのWebルートへJSONを送信し、CSRFトークン、バリデーションエラー、送信中・通信失敗の状態を扱います。リクエスト送信にはfetchとaxiosを使う方法を比較し、追加パッケージの有無やエラー処理の違いを整理します。
CodeIgniter 4のViewにVue.jsを組み込む
2026.08.20
CodeIgniter 4のView全体はサーバー側で描画したまま、一部分だけVue.jsを組み込む方法を解説します。Viteの開発サーバーからJavaScriptを読み込む方法、本番ビルドしたアセットをmanifestから取得してApacheで配信する方法、ControllerからViewへ渡したデータをVue.jsで検索する方法を確認します。
MarkdownをHTMLへ変換する方法とXSS対策:PHP・JavaScript・Vue.jsの実装例
2026.08.12
PHP のleague/commonmarkと、Node.js のMarked・DOMPurifyを使い、MarkdownをHTMLへ変換する方法を紹介します。ユーザー入力やAI生成Markdownを未信頼入力として扱い、Vue.js のv-htmlへ安全に表示するための設定と注意点も整理します。