Vue to Laravel Inertia
March 27, 2025 · AI Conversations
—
### ✅ 1. **Setup Laravel with Inertia and Vue**
If not already done:
composer require laravel/breeze --dev
php artisan breeze:install vue
npm install
npm run dev
php artisan migrate
This gives you Laravel + Inertia + Vue boilerplate with basic auth scaffolding.
—
### ✅ 2. **Move Vue Components**
Copy your Vue components from your standalone app into:
resources/js/Pages/
or if they are reusable components:
resources/js/Components/
Example:
# From your standalone app:
src/components/Navbar.vue
src/pages/Home.vue
# Move to:
resources/js/Components/Navbar.vue
resources/js/Pages/Home.vue
—
### ✅ 3. **Update Routing to Use Inertia**
Replace Laravel Blade views with Inertia routes:
**`routes/web.php`**
use InertiaInertia;
Route::get('/', function () {
return Inertia::render('Home'); // Matches Home.vue in Pages
});
—
### ✅ 4. **Add Page Layout If Needed**
If your Vue app had layout components (like a Navbar, Sidebar, etc), wrap your pages in a layout.
**`resources/js/Layouts/AppLayout.vue`**
<template>
<div>
<Navbar />
<main><slot /></main>
</div>
</template>
<script setup>
import Navbar from '@/Components/Navbar.vue'
</script>
Then use it in your pages:
**`resources/js/Pages/Home.vue`**
<template>
<AppLayout>
<h1>Welcome to Inertia</h1>
</AppLayout>
</template>
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue'
</script>
—
### ✅ 5. **Assets & Styles**
Move your `main.js` logic into Laravel’s entrypoint:
**`resources/js/app.js`**
If you have global styles:
– Move them to `resources/css/app.css` or import SCSS in `app.js`
– Example:
import './bootstrap';
import '../css/app.css'; // or app.scss
—
### ✅ 6. **Update API Calls**
If your Vue app uses Axios, you can use Laravel routes as APIs.
– Backend: Define routes in `routes/api.php`
– Frontend: Use Axios to call them (already included in Laravel Breeze)
—
### ✅ 7. **Run and Test**
php artisan serve
npm run dev
Visit `http://localhost:8000` and confirm your migrated Vue components are loading properly.
—
If you share your standalone Vue structure (or `App.vue`, routing setup), I can help with a more tailored conversion. Want to share that?
import './bootstrap';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { createI18n } from 'vue-i18n';
import { createApp, h } from 'vue';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import router from './router'
import en from './i18n/en.json';
import es from './i18n/es.json';
const savedLang = localStorage.getItem('lang') || 'en';
const i18n = createI18n({
locale: savedLang, // Set default locale
fallbackLocale: 'en', // Fallback locale in case the current locale is not available
messages: {
en,
es,
},
});
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => (title ? `${title} – ${appName}` : appName),
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue'),
),
setup({ el, App, props, plugin }) {
const app = createApp({ render: () => h(App, props) });
// Use plugins
app.use(i18n);
app.use(plugin);
app.use(ZiggyVue);
app.use(router);
app.config.globalProperties.t = (…args) => i18n.global.t(…args);
// Mount the app
app.mount(el);
},
progress: {
color: '#4B5563',
},
});
also use vue standalone router
—
## ✅ How to integrate **Vue Router with Laravel + Inertia**
You already have:
– Inertia setup
– Vue Router imported (`import router from './router'`)
– i18n support
– Ziggy
Here's a quick checklist to make it clean and safe:
—
### 1. ✅ Use Vue Router only for client-side sub-navigation
Avoid using Vue Router to replace Laravel routes. For example:
**Good use:**
– Tabs inside a page: `/dashboard/settings?tab=profile`
– Modals inside a page
– Step forms: `/checkout/step1`, `/checkout/step2`
**Avoid:**
– Full page routes like `/login`, `/dashboard` → should go via Laravel and Inertia.
—
### 2. ✅ Setup Vue Router
Make sure your `router/index.js` looks like this:
import { createRouter, createWebHistory } from 'vue-router';
import Profile from '@/Pages/User/Profile.vue';
import Settings from '@/Pages/User/Settings.vue';
const routes = [
{
path: '/dashboard/profile',
name: 'profile',
component: Profile,
},
{
path: '/dashboard/settings',
name: 'settings',
component: Settings,
},
];
const router = createRouter({
history: createWebHistory(), // must match Inertia history
routes,
});
export default router;
Then in a page component, e.g., `Dashboard.vue`:
<template>
<AppLayout>
<RouterView />
</AppLayout>
</template>
<script setup>
import AppLayout from '@/Layouts/AppLayout.vue';
</script>
You can now use “ and `router.push(…)` for sub-routes.
—
### 3. ✅ Use Laravel for top-level routing (via `web.php`)
Your Laravel routes should **only define the top-level route** like this:
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
});
In your `Dashboard.vue`, you'll now handle sub-routing via Vue Router.
—
### 4. ✅ Keep Inertia in control of `document.title`
Your setup:
title: (title) => (title ? `${title} - ${appName}` : appName),
That’s good. Just don’t use Vue Router’s “ logic — let Inertia own it.
—
### ✅ Summary
You're already doing great — just make sure:
– Laravel controls **top-level routes**
– Vue Router handles **client-side sub-views**
– Don't mix Inertia's page-based routing with Vue Router's full SPA routing
– Use `RouterView` inside Inertia pages if needed
—
If you want help refactoring your `router/index.js` or want a hybrid example (tabs with Vue Router inside an Inertia page), just drop the structure or ask — happy to walk through it!
GET openeratech.ddev.site
PHP 8.3.16 — Laravel 11.26.0
Expand
vendor frames
2 vendor frames collapsed
resources/views/app.blade.php
:25
require
61 vendor frames collapsed
public/index.php
:17
resources/views/app.blade.php :25
<script>
</script>
@routes
@vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
@inertiaHead
Unable to locate file in Vite manifest: resources/js/Pages/HomeView.vue
means Laravel Vite is trying to load `resources/js/Pages/HomeView.vue` **as if it were a JavaScript entry file**, which it’s not.
—
## 🧠 Why This Happens
You're using:
@vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
But `.vue` components **are dynamically imported** by Inertia/Vite using `resolvePageComponent()` in your `app.js`. You **should not include Vue files** in the `@vite()` directive directly.
—
## ✅ Solution
Replace this:
@vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
With just:
@vite(['resources/js/app.js'])
—
## ✅ Why This Fix Works
Your `app.js` already uses:
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue')
)
This makes Vite **auto-register all your Vue pages** via dynamic import, so Vite will build them correctly and add them to the manifest during `npm run dev` or `build`.
Including the `.vue` file directly in `@vite()` causes Vite to look for that file in the manifest as if it were a JS entry file — and it’s not, hence the error.
—
## ✅ Final Blade Template (`resources/views/app.blade.php`)
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
@routes
@vite(['resources/js/app.js'])
@inertiaHead
</head>
<body class="font-sans antialiased">
@inertia
</body>
</html>
—
Let me know if you want to use a fallback for missing components or dynamically show a "Not Found" Vue page instead — happy to help with that too.
resources/js/Pages/HomeView.vue
But it **wasn’t included in the Vite manifest**, which happens when either:
—
### 🚨 1. You included the `.vue` file directly in `@vite()`
**Fix this:**
**In `resources/views/app.blade.php`, replace:**
@vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"])
**with just:**
@vite('resources/js/app.js')
> ✅ `resolvePageComponent()` and `import.meta.glob()` already handle Vue page loading dynamically. No need to pass the `.vue` files manually to Vite.
—
### 🚨 2. File name mismatch
Ensure the page you're trying to render exists **exactly** with the same case-sensitive name.
For example:
If Laravel is trying to render:
resources/js/Pages/HomeView.vue
Then the file must **exist at that exact path** (case-sensitive!):
resources/js/Pages/HomeView.vue
Check for:
– Typo (e.g., `homeView.vue` vs `HomeView.vue`)
– Wrong folder (e.g., maybe it’s in `Views/` instead of `Pages/`)
– Filename casing if you're on a case-sensitive file system (like Linux)
—
### 🔍 3. Inertia page resolution logic
In your `app.js`, you're using:
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue')
)
So, if Laravel tries to render a page with:
return Inertia::render('HomeView');
Then `HomeView.vue` must exist inside `resources/js/Pages`.
—
### ✅ TL;DR Fix Recap
1. Update Blade layout:
@vite(['resources/js/app.js'])
2. Make sure:
resources/js/Pages/HomeView.vue
really exists with the correct name and location.
3. If not, update your Laravel route to use the correct component name:
return Inertia::render('CorrectFileName');
—
If you want to share the output of `ls resources/js/Pages` or the route code causing this, I can help spot the exact issue.