Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

psrz's avatar
Level 10

Vite build for production not working with inertia and Vue 2

Allright, I'll try to be as thorough as possible so this will be a long one...

  • Node 16.16.0
  • Npm 8.11.0
  • Php 8.1
  • Laravel 9
  • Inertia
  • Vue 2

For development is all good. We run npm run dev and php artisan serve and the application works without any issues (http://localhost:8000 in this case)

Then we run npm run build and bunch of public/build/assets are generated, the manifest.json too. There are no errors, just a warning about some chunk larger than 500kb

But we serve the app with apache and it doesn't work. The console on firefox shows something like this:

Navega a http://sgc.staging:8080/login
GET http://sgc.staging:8080/login [HTTP/1.1 200 OK 2066ms]
GET http://sgc.staging:8080/build/assets/app.4fb56957.css [HTTP/1.1 200 OK 1ms]
GET http://sgc.staging:8080/build/assets/app.e809327a.css [HTTP/1.1 200 OK 6ms]
GET http://sgc.staging:8080/build/assets/app.503a19f2.js [HTTP/1.1 200 OK 49ms]
GET http://sgc.staging:8080/build/assets/Login.8e343736.css [HTTP/1.1 200 OK 0ms]
GET http://sgc.staging:8080/build/assets/Login.9cdcc92f.js [HTTP/1.1 200 OK 1ms]
GET http://sgc.staging:8080/favicon.ico [HTTP/1.1 200 OK 1ms]
GET http://sgc.staging:8080/build/assets/vueComponentNormalizer.9ef17bb1.js [HTTP/1.1 200 OK 1ms]

TypeError: $7.observable is not a function app.503a19f2.js:5:24793

A few other nonsensical js errors and that's it. Just a blank screen. No 404s of any kind. It seems those assets aren't build the right way. Also, if we open a shell and run npm run dev that staging site works.

  • vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        createVuePlugin(),
    ],
});

  • package.json
{
    "private": true,
    "scripts": {
        "dev": "vite",
        "build": "vite build"
    },
    "devDependencies": {
        "laravel-vite-plugin": "^0.5.0",
        "postcss": "^8.1.14",
        "vite": "^3.0.0",
        "vite-plugin-vue2": "^2.0.2"
    },
    "dependencies": {
        "@inertiajs/inertia": "^0.11.0",
        "@inertiajs/inertia-vue": "^0.8.0",
        "@inertiajs/progress": "^0.2.7",
        "lodash": "^4.17.19",
        "axios": "^0.27",
        "bootstrap": "^4.6.2",
        "bootstrap-vue": "^2.22.0",
        "vue": "^2.7.8",
        "vue-template-compiler": "^2.7.8"
    }
}

  • app.js
import './bootstrap.js';

import Vue from 'vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import '../css/app.css';
import { createInertiaApp } from '@inertiajs/inertia-vue'
import { InertiaProgress } from '@inertiajs/progress'
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { BootstrapVue } from 'bootstrap-vue';

Vue.use(BootstrapVue);

Vue.prototype.$route = route;

createInertiaApp({
    resolve: async (name) => {

        let page = await resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue'));

        return page;
    },

    setup({ el, App, props, plugin }) {
        Vue.use(plugin)

        new Vue({
            render: h => h(App, props),
        }).$mount(el)
    },
})

InertiaProgress.init({
    color: 'blue',
    showSpinner: true,
});

I imagine it has something to do with the plugin for vue 2 and/or options to make the build. I also used a different plugin "@vitejs/plugin-vue2": "^1.1.2", but same story, all good for development, but no luck using the static build....

I would appreciate any help.

0 likes
5 replies
Erutan409's avatar

I did, eventually. I'm using Vite, Vue 2, and Vuetify. It took me a long time to get to a working version, but this is what I arrived at:

vite.config.js

const path = require('path');

import { defineConfig, splitVendorChunkPlugin } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue2';

import Components from 'unplugin-vue-components/vite';
import { VuetifyResolver } from 'unplugin-vue-components/resolvers';
import Inspect from 'vite-plugin-inspect';

export default defineConfig({
  plugins: [
    laravel({
      input: 'resources/js/app.ts',
      refresh: true
    }),
    vue(),
    Components({
      // directives: true,
      resolvers: [
        // Vuetify
        VuetifyResolver()
      ],
      dts: false
    }),
    splitVendorChunkPlugin(),
    Inspect()
  ],
  css: {
    preprocessorOptions: {
      sass: {
        additionalData: [
          `@import "@sass/variables.scss"\n` // this newline is intentional as it'll be parsed in-line with the first file
        ].join('\n')
      },
      scss: {
        additionalData: [
          `@import "@sass/variables.scss";` // this will correlate with the file below
        ].join('\n')
      }
    }
  },
  resolve: {
    alias: {
      '@js': path.resolve(__dirname, 'resources/js'),
      '@sass': path.resolve(__dirname, 'resources/sass'),
      '@images': path.resolve(__dirname, 'resources/images')
    }
  }
});

tsconfig.js

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "target": "esnext",
        "module": "esnext",
        "jsx": "preserve",
        "importHelpers": true,
        "moduleResolution": "node",
        "skipLibCheck": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "baseUrl": ".",

        /* Strict Type-Checking Options */
        "strict": false,
        "noImplicitAny": false,
        "strictNullChecks": false,
        "noImplicitThis": false,
        "alwaysStrict": false,

        /* Additional checks */
        "noUnusedLocals": true,
        "noUnusedParameters": false,
        "noFallthroughCasesInSwitch": true,

        "paths": {
            "@js/*": [
                "resources/js/*"
            ],
            "@sass/*": [
                "resources/sass/*"
            ],
            "@images/*": [
                "resources/images/*"
            ],
            "tslib": ["node_modules/tslib/tslib.d.ts"]
        },
        "lib": [
            "esnext",
            "dom",
            "dom.iterable",
            "scripthost"
        ],

        "types": ["vite/client"]
    },
    "include": [
        "resources/js/**/*.ts",
        "resources/js/**/*.tsx",
        "resources/js/**/*.vue"
    ],
    "exclude": [
        "node_modules"
    ]
}

package.json

{
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "devDependencies": {
    "@inertiajs/inertia": "^0.11.0",
    "@inertiajs/inertia-vue": "^0.8.0",
    "@types/lodash": "^4.14.184",
    "@vitejs/plugin-vue2": "^1.1.2",
    "axios": "^0.27",
    "laravel-vite-plugin": "^0.6.0",
    "lodash": "^4.17.19",
    "sass": "~1.32",
    "tslib": "^2.4.0",
    "typescript": "^4.8.3",
    "unplugin-vue-components": "^0.22.4",
    "vite": "^3.0.0",
    "vite-plugin-inspect": "^0.7.1",
    "vite-plugin-require": "^1.0.1",
    "vite-plugin-vue2": "^2.0.2",
    "vue": "^2.7.10",
    "vue-class-component": "^7.2.6",
    "vuetify": "^2.6.9"
  }
}

app.js

import Vue from 'vue';
import _ from 'lodash';

import { createInertiaApp, Link } from '@inertiajs/inertia-vue';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import vuetify from '@js/vue/plugins/vuetify';

import '@js/bootstrap';
import '@sass/app.scss';

import '@js/vue/plugins';
import App from '@js/vue/App.vue';
import AppProxy from '@js/vue/mixins/globals/AppProxy';

// I prefer `.` dot notation for pointing to my pages from the controller
const pageComponents = _(import.meta.glob('./vue/pages/**/*.vue'))
  .mapKeys((v, k) => _.chain(k).split('pages/').last().replace(/\.vue$/, '').split(/[\\/]+/).map(_.kebabCase).join('.').value())
  .value();

createInertiaApp({
  resolve: name => resolvePageComponent(name, pageComponents).then(p => {

    // shim in app layout
    // @ts-ignore
    (p.default || p).layout ??= App;

    return p;

  }),
  setup({ el, app, props, plugin }) {

    Vue.use(plugin);

    // global components
    // @ts-ignore
    Vue.component('Link', Link);

    // register global mixin(s) based on development environment
    if (import.meta.env.VITE_LOCAL_DEVELOPMENT) {
      Vue.mixin(AppProxy);
    }

    new Vue({
      vuetify,
      render: h => h(app, props)
    }).$mount(el);

  }
});

_variables.scss - Vuetify overloads (lots of people on the internet asking how to do this)

@import "vuetify/src/styles/styles";
@import "utility"; // my own utilities for creating my theme variables, etc.
@import "theme/colors"; // in sass declaration of my colors (outside of the vuetify.js plugin file)
@import "theme/dark"; // my overloads of https://github.com/vuetifyjs/vuetify/blob/fdfb6fc34d797d2798ae73b049f34e5098793caa/packages/vuetify/src/styles/settings/_dark.scss

/***** Begin Vuetify Overrides *****/

// Globals
$body-font-family: "IBM Plex Sans", sans-serif;
$font-size-root: 16px;

// Buttons
$btn-font-weight: 700;
$btn-sizes: (
  "x-large": 50
);
$btn-text-transform: none;

// Icon
$icon-size: 20px;

// Navigation
$navigation-drawer-border-width: 0;

// Text Field
$text-field-outlined-fieldset-border: 2px solid var(--v-hot-lava-base);

/***** End Vuetify Overrides *****/

I hope this helps anyone else trying to accomplish this exact tech stack (w/ versions). I have no apprehension to adopting Vue 3. But, Vuetify 3 isn't yet released as stable.

I spent wayyyyyyy too much time trying to get this tech stack to work with Laravel Mix. I really wanted it to work, too. I even have a couple published extensions on the site, so there was some personal investment, there. And there are too many disjointed, various examples all over StackOverflow and Github. This works and is ready to be further optimized for your needs.

If this write-up existed somewhere else (NOT in a github "example"), I couldn't find it.

Anyway, it's time to move on and it's clear that Vite is here to stay.

3 likes
vincent15000's avatar

@Erutan409 I will try this.

I have exactly the same problem using InertiaJS, Vite, Vue 3 and Element-Plus.

Working in dev mode, but as soon as I build, not working any more.

vincent15000's avatar

@Erutan409 I just solved this problem, but the origin is perhaps no the same as your one.

In my Vue JS code, with composition API, I used this to access to the different methods. I just had to remove this and it works fine.

Erutan409's avatar

@vincent15000 I'm not surprised my write-up wasn't an exact match for your configuration. I'm avoiding Vue 3 for the moment, given that Vuetify isn't fully updated for Vue 3.

Please or to participate in this conversation.