Let CodeIgniter serve the initial HTML page, and let Vue mount into a <div id="app"></div>. This gives you the benefits of CodeIgniter routing, sessions (Shield), CSRF, SEO-friendly pages, and future API/mobile support.
Recommended Project Structure
project-root/
├── app/
│ ├── Controllers/
│ │ └── Home.php
│ └── Views/
│ └── welcome_message.php
│
├── public/
│ ├── build/
│ │ ├── assets/
│ │ └── .vite/
│ ├── index.php
│ └── favicon.ico
│
├── frontend/
│ ├── src/
│ │ ├── main.js
│ │ ├── App.vue
│ │ └── router/
│ ├── index.html
│ └── vite.config.js
1. Build Vue
npm run build
This generates
public/build/
assets/
index-xxxxx.js
index-xxxxx.css
2. Create the CodeIgniter View
app/Views/welcome_message.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?= vite('frontend') ?>
</head>
<body>
<div id="app"></div>
</body>
</html>
3. Home Controller
<?php
namespace App\Controllers;
class Home extends BaseController
{
public function index()
{
return view('welcome_message');
}
}
4. Routes
$routes->get('/', 'Home::index');
Now visiting
http://localhost/
loads the CI page.
5. Vue Entry
src/main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Vue will mount inside
<div id="app"></div>
6. During Development (Vite)
Instead of loading the built assets, load the Vite development server.
Create a helper:
<?php
function vite(string $entry = 'frontend')
{
if (ENVIRONMENT === 'development')
{
return '
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/src/main.js"></script>
';
}
$manifest = json_decode(
file_get_contents(FCPATH.'build/.vite/manifest.json'),
true
);
$file = $manifest['src/main.js'];
$html = '';
if (isset($file['css'])) {
foreach ($file['css'] as $css) {
$html .= '<link rel="stylesheet" href="'.base_url('build/'.$css).'">';
}
}
$html .= '<script type="module" src="'.base_url('build/'.$file['file']).'"></script>';
return $html;
}
Then
<?= vite() ?>
works in both development and production.
7. Vue Router
Configure history mode.
import { createRouter, createWebHistory } from 'vue-router'
export default createRouter({
history: createWebHistory(),
routes: [
// routes
]
})
Register it:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
8. Catch All Routes in CodeIgniter
If Vue Router handles navigation, make CodeIgniter always return the same view:
$routes->get('/', 'Home::index');
$routes->get('(:any)', 'Home::index');
or
$routes->add('(:any)', 'Home::index');
This allows routes like
/
/about
/products
/dashboard
to all load the Vue application.
9. CodeIgniter + Shield Authentication
Since CodeIgniter serves the page:
Shield sessions work normally.
CSRF protection remains available.
You can expose authenticated user data to Vue before it mounts if needed:
<script>
window.App = {
user: <?= json_encode(auth()->user()) ?>
};
</script>
Then in Vue:
const user = window.App.user;
Architecture Recommendation
For CodeIgniter 4 + Vue + TailwindCSS + CodeIgniter Shield application (with future mobile API support), a clean architecture is:
Browser
│
▼
CodeIgniter Route
│
▼
Home Controller
│
▼
welcome_message.php
│
├── <div id="app"></div>
├── Inject authenticated user/session data
└── Load Vite-built JS/CSS
│
▼
Vue App
│
Vue Router
│
Axios/Fetch API
│
▼
CodeIgniter REST API
│
Shield Authentication
│
Database
This keeps CodeIgniter responsible for the initial request, authentication, and APIs, while Vue handles the entire client-side UI, which is scalable and aligns well with a future mobile application consuming the same REST APIs.