Custom Vue Configuration
Share a single Vue instance between LiVue and external Vue libraries to prevent conflicts.
The Problem: Multiple Vue Instances
When you use external Vue libraries (UI frameworks, plugins, etc.) alongside LiVue, you can end up with two separate Vue instances that don't communicate with each other.
What Happens
Common Errors
Why does this happen? Vue uses internal per-instance state (currentInstance, effectScope, etc.). When a library calls Vue functions, it looks for context in its Vue instance, but the actual rendering happens in LiVue's instance. The two don't share state.
The Solution: Shared Vue Instance
Enable Custom Mode to make LiVue use the same Vue instance as your external libraries. Instead of bundling its own Vue, LiVue will use the Vue from your node_modules.
Default Mode
// config/livue.php
'custom_vue' => false
// LiVue auto-injects livue.js
// Vue is bundled inside - standalone
// Works immediately, no config needed
// But: can't share Vue with other libs
Custom Mode
// config/livue.php
'custom_vue' => true
// You import livue.esm.js in app.js
// Vue comes from node_modules
// All libraries share ONE Vue instance
// Full control over Vue configuration
When Do You Need Custom Mode?
You need Custom Mode when using any Vue library that imports Vue internally. This includes but is not limited to:
UI Component Libraries
Vuetify, PrimeVue, Quasar, Element Plus, Naive UI, Ant Design Vue, Buefy, BootstrapVue
State Management
Custom Pinia stores (beyond LiVue's internal), Vuex (legacy)
Internationalization
Vue I18n, Fluent Vue, vue-i18next
Form Libraries
VeeValidate, FormKit, Vue Formulate
Utility Libraries
VueUse, Vue Query, Vue Apollo
Your Own Vue SFCs
Custom .vue components that use Vue's Composition API
Rule of thumb: If you npm install any package that has vue as a dependency or peer dependency, you likely need Custom Mode.
Setup Guide
Enable Custom Mode
This tells LiVue not to auto-inject its standalone bundle.
return [
'custom_vue' => true,
];
Configure Vite Alias
Since LiVue is installed via Composer (not npm), add an alias to import it cleanly.
import { defineConfig } from 'vite';
import { resolve } from 'path';
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(), // Required if you use .vue SFCs
],
resolve: {
alias: {
'livue': resolve(__dirname, 'vendor/livue/livue/dist/livue.esm.js'),
},
},
});
Import LiVue and Configure Plugins
Import the ESM bundle and use LiVue.setup() to register your plugins.
import './bootstrap';
// Import LiVue ESM bundle (uses Vue from node_modules)
import LiVue from 'livue';
// Example: Vuetify (any Vue plugin works the same way)
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
const vuetify = createVuetify({
// your vuetify options
});
// Register plugins with LiVue
LiVue.setup((app) => {
app.use(vuetify);
});
Include Your Bundle in the Layout
Make sure your compiled JavaScript is loaded in your layout.
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
{{ $slot }}
</body>
</html>
LiVue.setup() API
The setup() method receives a callback that runs for every Vue app instance LiVue creates. This includes root components and islands.
LiVue.setup((app) => {
// app is a Vue app instance (from createApp())
// Use standard Vue app API:
app.use(plugin); // Install a plugin
app.component('Name', C); // Register global component
app.directive('name', D); // Register global directive
app.provide('key', value); // Provide to all components
});
Execution Order
- 1.
createApp()- Vue app is created - 2. Pinia is installed (required by LiVue internally)
- 3. Your setup callback is called
- 4. Built-in LiVue directives are registered
- 5.
app.mount()- Vue app is mounted
Examples
These examples show common use cases. The pattern is always the same: import the library, configure it, and register it in the setup callback.
import LiVue from 'livue';
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
const vuetify = createVuetify({
components,
directives,
theme: { defaultTheme: 'dark' }
});
LiVue.setup((app) => app.use(vuetify));
import LiVue from 'livue';
import PrimeVue from 'primevue/config';
import Aura from '@primevue/themes/aura';
LiVue.setup((app) => {
app.use(PrimeVue, {
theme: { preset: Aura }
});
});
import LiVue from 'livue';
import { createI18n } from 'vue-i18n';
const i18n = createI18n({
locale: 'en',
messages: {
en: { greeting: 'Hello' },
it: { greeting: 'Ciao' },
}
});
LiVue.setup((app) => app.use(i18n));
import LiVue from 'livue';
import { createVuetify } from 'vuetify';
import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia';
const vuetify = createVuetify({...});
const i18n = createI18n({...});
const pinia = createPinia(); // Additional stores
LiVue.setup((app) => {
app.use(vuetify);
app.use(i18n);
app.use(pinia); // Won't conflict with LiVue's internal Pinia
});
import LiVue from 'livue';
import MyChart from './components/MyChart.vue';
import DataTable from './components/DataTable.vue';
LiVue.setup((app) => {
app.component('MyChart', MyChart);
app.component('DataTable', DataTable);
});
// Now usable in LiVue templates:
// <MyChart :data="chartData" />
import LiVue from 'livue';
LiVue.setup((app) => {
app.directive('focus', {
mounted(el) { el.focus(); }
});
app.directive('tooltip', {
mounted(el, binding) {
// Initialize tooltip library
}
});
});
Preventing FOUC (Flash of Unstyled Content)
When using Vue UI frameworks, you may see a flash of unstyled content before the framework's styles load. Use Vue's built-in v-cloak directive to prevent this:
<!DOCTYPE html>
<html>
<head>
<!-- This MUST come before any other CSS/JS -->
<style>[v-cloak] { display: none; }</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body v-cloak>
{{ $slot }}
</body>
</html>
How it works: The inline <style> loads instantly and hides the body. When Vue mounts, it automatically removes the v-cloak attribute, making the page visible with all styles applied.
How It Works Internally
Two Bundles
dist/livue.jsStandalone bundle with Vue included. Used in Default Mode. Auto-injected by LiVue middleware.
dist/livue.esm.jsESM bundle without Vue. Imports Vue as external dependency. Used in Custom Mode.
What custom_vue: true Does
- 1. LiVue middleware skips auto-injection of the standalone bundle
-
2.
You import
livue.esm.jsin your own JavaScript -
3.
This bundle uses
import { ... } from 'vue'internally -
4.
Vite resolves all
'vue'imports to the samenode_modules/vue - 5. Result: LiVue and all your libraries share one Vue instance
Important Notes
- • LiVue is distributed via Composer (Packagist), not npm
-
•
The Vite alias maps
'livue'to the ESM bundle in vendor -
•
LiVue.setup()is called for every Vue app (root + islands) - • Pinia is always installed before your callback (LiVue uses it internally)
Troubleshooting
"withDirectives can only be used inside render functions"
This is the classic "two Vue instances" error.
Fix: Enable custom_vue: true and import LiVue from your app.js.
"inject() can only be used inside setup()"
A plugin is trying to inject dependencies but can't find the Vue context.
Fix: Same as above - you have multiple Vue instances.
Components not receiving theme/styles
The plugin is installed but components don't have access to it.
Fix: Ensure LiVue.setup() is called before DOMContentLoaded. The callback must be registered early, before any components mount.
LiVue not working at all after enabling custom_vue
The page loads but LiVue components don't become reactive.
Fix: Check that: 1) Your layout includes @vite(['resources/js/app.js']), 2) app.js imports LiVue, 3) Vite alias is configured correctly.
Module not found: 'livue'
Vite can't resolve the LiVue import.
Fix: Add the alias in vite.config.js pointing to vendor/livue/livue/dist/livue.esm.js.