CodeIgniter 4のViewにVue.jsを組み込む
作成日:2026.08.20
CodeIgniter 4のView全体はサーバー側で描画したまま、一部分だけVue.jsを組み込む方法を解説します。Viteの開発サーバーからJavaScriptを読み込む方法、本番ビルドしたアセットをmanifestから取得してApacheで配信する方法、ControllerからViewへ渡したデータをVue.jsで検索する方法を確認します。
目次
CodeIgniterで画面を作っていると、ページ全体はサーバー側のViewで表示しつつ、検索欄や入力フォームなど一部だけをリアクティブにしたいことがあります。
今回は、CodeIgniter 4のViewへVue.jsを部分的に組み込み、商品名を検索する画面を作ります。アプリケーション全体をSPAへ変更するのではなく、商品検索の部分だけをVue.jsで動かす構成です。
Vue.jsのコードはViteでビルドします。開発時はViteの開発サーバーからJavaScriptを読み込み、本番時はViteのmanifestを使って、Apacheが配信するビルド済みファイルをCodeIgniterのViewへ読み込ませます。
前提
この記事では、次の環境を前提にします。
- OS: Windows 11
- CodeIgniter: 4.7.x
- PHP: 8.2以上
- Composer
- Node.js: 20.19以上、または22.12以上
- npm
- Vue.js
- Vite
- Webサーバー: Apache
- ターミナル: PowerShell
CodeIgniter 4.7.xでは、PHP 8.2以上とintl、mbstring拡張が必要です。詳しくはCodeIgniter 4のサーバー要件を参照してください。
Apacheは、CodeIgniterプロジェクトのpublicディレクトリを公開する設定が済んでいるものとします。今回はApacheをhttp://localhost:8000で起動します。VirtualHostや.htaccessの設定方法は、この記事では扱いません。
CodeIgniterのインストール方法は、CodeIgniter公式のインストールガイドを参照してください。既存のCodeIgniterプロジェクトを使う場合は、プロジェクトルートをPowerShellで開いた状態から始めます。
CodeIgniterのViewとVue.jsを組み合わせる
CodeIgniterでは、ControllerからViewへデータを渡し、ViewでHTMLを生成します。今回は、この流れを維持したまま、商品検索用の要素だけをVue.jsの管理対象にします。
CodeIgniter公式ドキュメントでも、Viewは通常app/Viewsへ配置し、Controllerからデータを渡して表示する構成として説明されています。View全体をVue.jsのアプリケーションにする必要はありません。
今回の処理の流れは、次のようになります。
ブラウザ
↓ http://localhost:8000/products へアクセス
Apache
↓
CodeIgniterのルート
↓
Productsコントローラー
↓ 商品データをViewへ渡す
products/index.php
├─ ページ全体のHTMLを表示
├─ 商品データをJSONとしてVue.jsへ渡す
└─ 商品検索のマウント先を用意
↓
Vue.jsのProductSearchコンポーネント
└─ 商品名で表示結果を絞り込む
Vue.jsでは、createAppでアプリケーションを作成し、mountで指定したDOM要素へ表示します。サーバーで生成した大きなページの一部だけをVue.jsで扱う場合は、ページ全体ではなく必要な要素へ小さなアプリケーションをマウントできます。詳しくはVue.js公式のアプリケーション作成ガイドを参照してください。
プロジェクトの構成
既存のCodeIgniterプロジェクトへ、Viteのソースファイルを追加します。今回は、Viteのソースをresources/jsへ置き、ビルド結果をCodeIgniterの公開ディレクトリであるpublic/buildへ出力します。
project-root/
├─ app/
│ ├─ Config/Routes.php
│ ├─ Controllers/Products.php
│ └─ Views/
│ └─ products/index.php
├─ public/
│ └─ build/ # npm run buildで生成
├─ resources/
│ └─ js/
│ ├─ app.js
│ └─ components/
│ └─ ProductSearch.vue
├─ package.json
└─ vite.config.js
publicはブラウザからアクセスできるディレクトリです。CodeIgniter公式ドキュメントでも、公開対象のファイルやアセットはpublicへ置き、WebサーバーのDocumentRootをそこへ向ける構成が説明されています。
Vue.jsとViteを導入する
パッケージをインストールする
PowerShellで、CodeIgniterプロジェクトのルートディレクトリへ移動します。
cd D:\path\to\codeigniter-project
Vue.js、Vite、Vue用のViteプラグインをインストールします。
npm install vue
npm install --save-dev vite @vitejs/plugin-vue
VueのSingle-File Component(SFC)である.vueファイルを扱うため、@vitejs/plugin-vueをViteへ設定します。
vite.config.jsを設定する
プロジェクトルートにvite.config.jsを作成し、次の内容を記述します。
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
cors: {
origin: 'http://localhost:8000',
},
},
build: {
outDir: 'public/build',
emptyOutDir: true,
manifest: 'manifest.json',
rollupOptions: {
input: 'resources/js/app.js',
},
},
});
今回の設定では、Viteのポートを指定していないため、デフォルトの5173で起動します。CodeIgniterの画面はApacheの8000番ポートから表示するため、Viteの開発サーバーには、Apacheからのページで読み込めるようCORSの許可元を指定しています。5173番ポートが使用中の場合は、Viteが別のポートを表示するため、Viewの読み込み先も合わせて変更します。
build.outDirには、ビルド結果の出力先を指定します。manifestにファイル名を指定すると、今回はpublic/build/manifest.jsonとしてmanifestを生成できます。
従来型のバックエンドとViteを組み合わせる場合の開発用スクリプトやmanifestについては、Vite公式のBackend Integrationも参照してください。
商品データを用意する
Controllerを作成する
商品一覧を表示するControllerを作成します。今回はデータベースを使わず、説明用の配列を使用します。
app/Controllers/Products.phpを作成し、次の内容を記述します。
<?php
namespace App\Controllers;
class Products extends BaseController
{
public function index()
{
$products = [
[
'id' => 1,
'name' => '脚付き将棋盤',
'price' => 128000,
'stock' => 5,
],
[
'id' => 2,
'name' => '折り畳み将棋盤',
'price' => 3980,
'stock' => 18,
],
[
'id' => 3,
'name' => '将棋駒セット',
'price' => 6800,
'stock' => 12,
],
[
'id' => 4,
'name' => '木製駒台',
'price' => 2480,
'stock' => 0,
],
];
return view('products/index', [
'products' => $products,
]);
}
}
実際のアプリケーションでは、ここでModelなどから商品を取得します。Vue.jsへ渡すのは、検索画面で表示する商品情報だけにしてください。パスワード、内部管理用のフラグ、権限情報などを、画面表示に必要だからという理由でまとめて渡してはいけません。
ルートを追加する
app/Config/Routes.phpへ、商品一覧を表示するルートを追加します。
$routes->get('products', 'Products::index');
CodeIgniterのルーティング設定については、CodeIgniter公式のURI Routingを参照してください。
CodeIgniterのViewを作成する
app/Views/products/index.phpを作成します。まず、Controllerから渡された商品データをJSONへ変換します。
<?php
$productsJson = json_encode(
$products,
JSON_UNESCAPED_UNICODE
| JSON_HEX_TAG
| JSON_HEX_AMP
| JSON_HEX_APOS
| JSON_HEX_QUOT
| JSON_THROW_ON_ERROR
);
?>
JSONをHTML属性へ埋め込む場合は、JSONとしてのエスケープとHTMLとしてのエスケープを分けて考えます。JSON_HEX_*でJSON内のHTMLに関係する文字を変換したうえで、属性値の出力時にCodeIgniterのescを使います。
続けて、Viewの本文へ商品検索のマウント先を追加します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>商品検索</title>
</head>
<body>
<main>
<h1>商品検索</h1>
<div
id="product-search"
data-products="<?= esc($productsJson) ?>"
></div>
</main>
<?php if (ENVIRONMENT === 'development'): ?>
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/resources/js/app.js"></script>
<?php else: ?>
<?php
$manifestPath = FCPATH . 'build/manifest.json';
if (! is_file($manifestPath)) {
throw new RuntimeException('Viteのmanifestが見つかりません。');
}
$manifest = json_decode(
file_get_contents($manifestPath),
true,
512,
JSON_THROW_ON_ERROR
);
$entry = $manifest['resources/js/app.js'] ?? null;
if (! is_array($entry) || empty($entry['file'])) {
throw new RuntimeException('Viteのエントリーポイントがmanifestにありません。');
}
?>
<?php foreach ($entry['css'] ?? [] as $css): ?>
<link rel="stylesheet" href="<?= esc(base_url('build/' . $css)) ?>">
<?php endforeach; ?>
<script type="module" src="<?= esc(base_url('build/' . $entry['file'])) ?>"></script>
<?php endif; ?>
</body>
</html>
開発時は、Viteのクライアントとエントリーポイントを直接読み込みます。本番時は、public/build/manifest.jsonからエントリーポイントのJavaScriptとCSSを取得します。
今回の例では単一のエントリーポイントだけを扱っているため、manifestのfileとcssを読み込んでいます。複数のエントリーポイント、動的import、共通チャンクを使う場合は、manifestのimportsも考慮したアセット読み込み処理が必要です。
Vue.jsのエントリーポイントを作成する
resources/js/app.jsを作成します。
import 'vite/modulepreload-polyfill';
import { createApp } from 'vue';
import ProductSearch from './components/ProductSearch.vue';
const mountPoint = document.querySelector('#product-search');
if (mountPoint) {
const products = JSON.parse(
mountPoint.dataset.products ?? '[]'
);
createApp(ProductSearch, {
products,
}).mount(mountPoint);
}
data-products属性に保存したJSONをdataset.productsから読み込み、JSON.parseでJavaScriptの配列へ戻しています。マウント先が存在しないページでも同じエントリーポイントを読み込めるよう、要素の存在を確認してからVueアプリケーションを作成します。
商品検索コンポーネントを作成する
resources/js/components/ProductSearch.vueを作成します。
<script setup>
import { computed, ref } from 'vue';
const props = defineProps({
products: {
type: Array,
default: () => [],
},
});
const keyword = ref('');
const filteredProducts = computed(() => {
const normalizedKeyword = keyword.value.trim();
if (normalizedKeyword === '') {
return props.products;
}
return props.products.filter((product) => {
return product.name.includes(normalizedKeyword);
});
});
const formatPrice = (price) => {
return new Intl.NumberFormat('ja-JP').format(price);
};
</script>
<template>
<section>
<label for="product-keyword">商品名</label>
<input
id="product-keyword"
v-model="keyword"
type="search"
placeholder="商品名を入力"
>
<p>{{ filteredProducts.length }}件</p>
<ul v-if="filteredProducts.length > 0">
<li v-for="product in filteredProducts" :key="product.id">
<strong>{{ product.name }}</strong>
- {{ formatPrice(product.price) }}円
(在庫: {{ product.stock }})
</li>
</ul>
<p v-else>該当する商品はありません。</p>
</section>
</template>
<style scoped>
section {
max-width: 640px;
}
input {
display: block;
margin-top: 8px;
padding: 8px;
width: 100%;
}
li {
margin-top: 8px;
}
</style>
keywordは入力欄の値を保持するリアクティブな値です。computedで検索結果を作るため、入力内容が変わると表示対象も更新されます。
今回の検索は、前後の空白を取り除き、商品名に入力文字列が含まれるかを確認するだけのシンプルな実装です。大文字・小文字の吸収、全角・半角の統一、表記ゆれの吸収などは行いません。
開発時に動作を確認する
まず、CodeIgniterのViewを表示するApacheを起動します。環境によって起動方法は異なりますが、今回はApacheから次のURLでアクセスできる状態を前提にします。
http://localhost:8000/products
別のPowerShellで、プロジェクトルートからViteの開発サーバーを起動します。
npm run dev
まだpackage.jsonにスクリプトがない場合は、次のように追加します。
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}
ブラウザでhttp://localhost:8000/productsを開き、商品一覧が表示されることを確認します。検索欄へ「将棋」などの文字を入力すると、商品名に該当する商品だけが表示されます。
Vue.jsのファイルを変更したときは、Viteの開発サーバーが変更を検知します。画面が表示されない場合は、ブラウザの開発者ツールだけでなく、Viteを起動したPowerShellのエラーも確認してください。
本番用にビルドする
開発時の確認が終わったら、プロダクション用のファイルを生成します。
npm run build
成功すると、public/buildにJavaScript、CSS、manifestが生成されます。例えば、次のような構成になります。
public/build/
├─ assets/
│ ├─ app-xxxxxxxx.js
│ └─ app-xxxxxxxx.css
└─ manifest.json
ファイル名にはハッシュが含まれるため、Viewへapp-xxxxxxxx.jsのような名前を直接書かないでください。ビルドのたびにファイル名が変わる可能性があるため、先ほどの例のようにmanifestからエントリーポイントを取得します。
Apache上で本番アセットを確認する
ApacheのDocumentRootがCodeIgniterのpublicを指している場合、public/build/assets/app-xxxxxxxx.jsは、概念的には次のURLから配信されます。
http://localhost:8000/build/assets/app-xxxxxxxx.js
本番モードで/productsへアクセスすると、ViewのENVIRONMENTによる分岐で、Vite開発サーバーではなくpublic/build/manifest.jsonから読み込んだファイルが使われます。
CodeIgniterのpublicはブラウザから公開するディレクトリであり、appやvendorを直接公開する場所ではありません。ApacheのDocumentRootをプロジェクトルートへ向けると、アプリケーションのソースコードを公開してしまう可能性があるため注意してください。
今回の記事では、ApacheのVirtualHostや.htaccessの具体的な設定は扱いません。ただし、CodeIgniterのルートが404になる場合は、Apacheのmod_rewriteやpublic/.htaccessの設定が原因になっていないか確認します。公式のCodeIgniterのURLガイドにも、ApacheでURLを書き換える場合の注意点が掲載されています。
ブラウザ内検索とサーバー側検索を使い分ける
今回の例では、Controllerから商品データを最初に受け取り、ブラウザ内で商品名を絞り込んでいます。この構成は、初期表示に必要な商品数が少なく、検索対象をブラウザへ渡しても問題ない場合に向いています。
次のような場合は、ブラウザ内検索ではなくサーバー側検索を検討してください。
- 商品件数が多く、初期表示で全件を渡すとHTMLやJSONが大きくなる。
- 検索結果をユーザーの権限によって制御する必要がある。
- 検索時点で最新の在庫や価格を取得したい。
- ページネーションや全文検索が必要になる。
サーバー側検索へ変更する場合は、Vue.jsからAPIへリクエストする方法や、通常のフォーム送信でCodeIgniterのControllerへ検索語を渡す方法があります。今回の目的はViewへのVue.jsの部分導入なので、APIやページネーションの実装までは扱いません。
うまく動かない場合
商品検索が表示されない
Viewにid="product-search"の要素があるか、Viteのエントリーポイントが読み込まれているかを確認します。開発時であれば、ブラウザの開発者ツールのNetworkタブで、次のURLが読み込まれているかを確認できます。
http://localhost:5173/@vite/client
http://localhost:5173/resources/js/app.js
ViteのJavaScriptを読み込めない
Viteを起動しているか、ポートが5173から変更されていないか確認します。また、vite.config.jsのCORSの許可元が、実際にApacheへアクセスしているURLと一致しているか確認してください。
本番でJavaScriptが404になる
npm run buildを実行したか、public/build/manifest.jsonが存在するかを確認します。次に、manifestのfileに記録されたファイルが、public/build以下に存在するか確認します。
ApacheのDocumentRootがpublicではない場合、ファイルが生成されていてもブラウザからアクセスできません。Apacheの設定と、ブラウザから直接アクセスしたURLの対応を確認してください。
商品データのJSONを解析できない
ブラウザのElementsタブで、data-products属性が出力されているか確認します。JSON文字列を手作業で編集したり、HTMLエスケープを外したりせず、PHP側のjson_encodeとescを使って出力してください。
また、ブラウザへ渡すデータに、画面表示に不要な個人情報や認証情報が含まれていないかも確認します。HTMLへ出力した値は、Vue.jsで隠しても利用者のブラウザへ届いています。
まとめ
CodeIgniter 4のViewを維持したまま、商品検索の部分だけをVue.jsで動かす方法を確認しました。
Vue.jsのコードはViteで管理し、開発時はlocalhost:5173のVite開発サーバーから読み込みます。本番時はnpm run buildでpublic/buildへ出力し、manifestからハッシュ付きのJavaScriptとCSSを取得します。
ApacheではCodeIgniterのpublicディレクトリを公開し、Viewやアプリケーションのソースコードを直接公開しないようにします。商品数が少ない場合は今回のようなブラウザ内検索を使い、データ量や権限、最新性が重要になった場合はサーバー側検索へ切り替えるとよいでしょう。
奈良市を拠点に、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を使う方法を比較し、追加パッケージの有無やエラー処理の違いを整理します。
Laravel 13のBladeにVue.jsを組み込む
2026.08.18
Laravel 13のBlade画面にVue.jsを部分的に組み込み、Controllerから渡した商品データを使って商品名を検索する方法を解説します。Viteの設定、BladeからVue.jsへの初期データの渡し方、Js::fromとdata-*属性の比較、npm run devによる確認方法、ブラウザ内検索とサーバー側検索の使い分けを扱います。
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へ安全に表示するための設定と注意点も整理します。