Laravel, Vue and Inertia with Vite

Laravel, Vue and Inertia with Vite
Photo by Ben White / Unsplash

Webpack is great… but there might be better.

This post was originally published on Medium on the 22nd October, 2021. This article has been recently updated to reflect changes in Tailwind version 3.

But first… some shameless self promotion

Vite is a fantastic piece of software and if you’re keen to see how I got it working with Laravel and Inertia, then read on. However, if you just want it to work, then I’ve written a simple Laravel package that will “boiletplate” this all out for you. It’s called “Vitamin” (clever, I know). I’ve managed to work out how to get around a lot of the stumbling blocks so it’s a fairly safe solution to all this, and will get you there in just a few minutes. Check it out here.

Also, a shout out

When I started experimenting with Vite, This article by Sebastian De Deyne got me 90% of the way. There were a few things that didn’t work for me and I had to figured out a bunch, but you may notice some similarities. There’s also a laravel-vite package that’s worth looking at.

Vite is still quite new and there’s isn’t too much documentation or tooling around its use with Laravel out there. But it’s a great tool and it works really well with things I already use. Here’s hoping you find this post useful as well.

Into the fray…

I’m a big Laravel fan. Not only has it helped me become a better programmer, but it’s now also a central component of my own business. I can’t really see myself giving up Laravel any time in the near future.

Laravel comes with its own templating language called Blade. Blade is great and I’ve built many a project with it. At some point I also started using Vue to sprinkle a little interactive magic into those blade files. It’s a nice way to go, but I felt like there were other solutions. Don’t get me wrong, Blade is great and I still use it plenty, I was just interested in learning something new.

Over the years I’ve also built a few single-page apps using Laravel and Vue, but I’ve never really been a big fan of that path. Vue-router and Vuex are really great tools, but making it all work with Laravel just felt awkward. So when Johnathan Reinink wrote his first blog post on what would eventually become Inertia, I was hooked.

At the time, I worked for a small web developement company, and as lead developer I had a lot of sway over our tech choices, so being the tech bully I can sometimes be, I managed to get us to buld a single project using the ideas that Johnathan spoke about. It was like a breath of fresh air. We got all the magic of Laravel but could build our front-end completely in Vue single-file components. When Inertia was eventually released it was a no-brainer. It made perfect sense for me.

So over the last little while, my tech stack has been almost exclusively Laravel, Vue, and Inertia. This all works because Webpack and Laravel Mix are great. There’s also not really a lot of initial set up needed. Laravel even comes with a “one-click install” for this setup these days.

However, There’s a big shortfall here: It’s slow. It works wonders when your project is small and has a fairly simple UI, but as soon as it starts getting a bit more complex, things slow down. This is because each time you make a change, your entire front-end gets re-bundled. This means that you could end up waiting upto 15 seconds each time you make a change, and that’s just not cool.

I figured I needed some way to speed things up. As a big Vue fan, I’d already heard about Vite, but didn’t really give it much thought at the time because I was happy with my set up. I didn’t think I needed to change anything. Well, after getting frustrated while working on a very large front-end project, I decided it was time to take another look. The speed improvement is insane. My productivity has improved tenfold. I spend zero time waiting for things to bundle and my old 2017 Macbook CPU is really grateful. I don’t hear that aweful fan nearly as much now.

Vite is fast because it leverages a fairly modern browser feature called ES modules. All modern browsers support modules now so your front-end components get injected into the browser each time you make a change. No need to bundle all your components together, so no waiting. Changes are close to instant.

Setting up Vite with Laravel takes a little work, but it really is worth the effort. There’s a few articles out there that describe getting Laravel and Vite working, and the best one I found was from Sebastian De Deyne (link above).

I’ve been building on Sebastian’s idea and I think I now have a Vite based build pipeline that suites me well. So this article will describe the initial setup and some of the pitfalls I almost got caught up in.

Before you begin

It’s important to note that this is fairly opinionated. It works well for me, but there are some caveats. It’s up to you what you’re willing to put up with and remember Webpack has been around a lot longer than Vite. If you’re uncomfortable using newer tools, then this might not be for you.

I’ve also found that some changes don’t get inserted into the browser automatically. making changes to your template in a Vue component works fine, but adding a child component, or making changes to any scripts require a browser reload. This is because of how Vue components are booted. I don’t mind reloading my browser every so often.

First steps

This is a Laravel approach to Vite, so obviously you’ll need a Laravel app. I’ve had success moving an older Laravel Mix app over, so you can do it on something that already exists, but to make these first attempts easier I’d suggest starting with a fresh app. It’s also important to note that I’m a Laravel Valet user and I wanted to continue using that.

Once you have a new Laravel app up and running and you can get to it through your browser, you’re ready to go.

First, you can remove all the bits you don’t need anymore. You’re not using Webpack or Laravel Mix anymore, so you can remove any associated files. You can also remove some of the default Laravel stuff:

webpack.mix.js
resources/views/welcome.blade.php

You’ll also want to remove the Laravel Mix dependency from package.json.

Next, you’ll need to install dependencies for Inertia, Vite, Vue and Tailwind. (You don’t need to use Tailwind if that’s not your thing, but it’s my go-to now days, so I’ve included it here). The only PHP dependency you’ll need forr now is Inertia, so:

composer require inertiajs/inertia-laravel

Then the Node dependencies for Inertia, Vue, Vite and Tailwind:

yarn add @inertiajs/inertia @inertiajs/inertia-vue3 vue@next vite @vitejs/plugin-vue @vue/compiler-sfc autoprefixer@latest postcss@latest tailwindcss@latest --dev

Now let’s get the Tailwind, Inertia and Vue configuration bits out of the way. Tailwind is fairly simple:

npx tailwindcss init -p

In your resources/css/app.css add:

@tailwind bade;
@tailwind components;
@tailwind utilities;

In the newly created tailwind.config.js change the content array to:

module.exports = {
    content: [
        './resources/js/**/*.{vue,js}',
        './resources/views/**/*.blade.php'
    ]
}

Next is Vue and Inertia, and it’s easy to start with the defaults found in the Inertia documentation. When dealing with larger apps, it can be really handy to split your JavaScript up into separate files so that each file is much smaller than using one large bundle. These separate files can then be dynamically imported into the application only when they’re needed. Vite supports dynamic imports by default, but you’ll need to make a change first.

You can’t use the require method anymore because that’s a Webpack implementation. Instead, you need to use import instead. You can then use the import.meta.glob method to only import the pages we want to split. The standard async-await JavaScript feature comes in handy here to pass a function to theresolve on createInertiaApp.

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';

import '../css/app.css';

createInertiaApp({
    resolve: async (name) => {
        const pages = import.meta.glob('./Pages/**/*.vue');
        
        return (await pages[`./Pages/${name}.value`]()).default;
    },
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el)
    },
});
app.js
If you’re just starting out with Inertia, one of the things you’ll miss is Laravel’s super handy `route` helper function. If that sounds like you then I cannot recommend Ziggy highly enough. It takes all your Laravel routes and makes them available to your front end. It then gives you a similar `route` helper function. I use it in nearly every Laravel project.

Vite Configuration

Vite is configured using a vite.config.js file placed at the root of your project. TypeScript is supported out of the box, so you could name the file vite.config.ts. I’ve only recently started with TypeScript and still deciding if it’s all worth it. I’m not going that route for now, but if I feel more confident in the future then I’ll update this with some info. If you’re a TypeScript pro, then I’m sure you’ll manage to make the necessary changes.

Before you get to configuration, it’s important to know that Vite will still bundle your JavaScript for production. There are some details about this here. Vite does requires a slightly different mindset if you’re used to how Laravel Mix works, but it’s easy to understand once you know why. During development, Vite actually spins up a web server to serve your assets. Remember that there’s no bundling, so your browser needs to know how to get hold of them. This is the bit that makes Vite magical. But it does mean that there’s a few extra steps to setting it up.

Setting up Vite is fairly simply. If you have any experience configuring Webpack, this will seem like a breath of fresh air. Here’s a basic vite.config.js configuration file to start with:

export default ({command}) => ({
    // config goes here...
});

Vite will inject the command that is run into the config file so you can use that information to change the configuration depending on the environment you’re currently working in. This is really handy. The commandparameter will be either serve or build depending on how you run vite from the command-line, which would normally look something like this:

npx vite build
// or
npx vite serve

You’ll use this command parameter soon.

By default, Vite assumes that the base directory is the public path your assets are served from. You might be used to having a js and css directory, but I’d suggest using a single build directory for all your assets. Doing so makes it easier to configure and it’s easier to just ignore a whole directory when you eventually commit this to your repo. You can tell Vite to do this by setting the base config option:

export default ({command}) => ({
    base: '/build/',
});

The catch here is that this is only for production. During development, you don’t need this base directory because Vite will serve your unbundled assets through its own server. This is where that command parameter comes into play:

export default ({command}) => ({
    base: command === 'serve' ? '' : '/build/',
});

Now when you run Vite in development, it will spin up a web server to serve development assets and wont use the build path. But when bundling for production, the build path will be used when generating a manifest (we’ll get to this soon).

The next setting we need to look at is publicDir. As the Vite documentation states, this is the directory that Vite will serve at / during dev. When building for production, assets in this directory will be copied to outDir(which we’ll get to next). Since we already have a public directory, we don’t really need Vite to copy anything, so we can set this to false:

export default ({command}) => ({
    base: command === 'serve' ? '' : '/build/',
    publicDir: false,
});

That’s it for the development config, now we just need to add some stuff for when you run build. Vite’s build config is all under the build key and there’s a few things you’ll want to set here. If you’re familiar with Laravel Mix, then you’ll know what the mix-manifest.json file is for. You can get Vite to do the same thing by setting `manifest` to `true`:

export default ({command}) => ({
    base: command === 'serve' ? '' : '/build/',
    publicDir: false,
    build: {
        manifest: true,
    }
});

We also need to tell Vite where to place the bundled assets. We can do so using the `outDir` option:

export default ({command}) => ({
    base: command === 'server' ? '' : '/build/',
    publicDir: false,
    build: {
        manifest: true,
        outDir: 'public/build',
    }
});

So now you’ve told Vite that the base path for all assets is /build/ which will be used when you bundle assets for production. So if your app will eventually be at https://my.app then https://my.app/build will be where you’ll find all the bundled assets. You’ve also told it that when building for production, to place everything in the public/build directory.

The last thing you need to do is specify where the main JavaScript file is. Vite uses Rollup in the same way that Laravel Mix uses Webpack. You can get Vite to pass all sorts of stuff to Rollup, but the only thing you need to worry about right now is the entry point. You can set this using the inputRollup config option and passing it to rollupOptions:

export default ({command}) => ({
    base: command === 'serve' ? '' : '/build/',
    publicDir: false,
    build: {
        manifest: true,
        outDir: 'public/build',
        rollupOptions: {
            input: 'resources/js/app.js',
        }
    }
});

That’s a good start. If you run npx vite serve now, you’ll get something like this:

You should now have a dev server running at https://localhost:3000/. If you run npx vite build, Vite will bundle your assets ands you should get a public/build directory with your bundled assets. Remember that Vite doesn’t know anything about your Laravel app, and it shouldn’t. It’s only concerned with serving and bundling front-end assets.

Valet

I like Valet. I work on a bunch of different projects on a daily bais, so Valet makes that a little simpler. There might be better solutions here, but Valet feels natural to me. To make this work with Valet, you need to tell Vite what host you’re using otherwise you’ll come up agaist a whole bunch of CORS errors.

You can do this by adding a --host option when running Vite:

npx vite serve --host=mysite.test

Working this way lead to a small problem. Using the FontSource library, I found that Vite would not be able to find the font files when running in dev. Production builds were fine, but it kept getting the path wrong in dev. I managed to solve this by using a custom Valet driver. If you’re not sure what I mean, Valet uses drivers to help serve applications built with different frameworks. And there is a default Laravel driver that we can extend. Create a new PHP file at your project root called LocalValetDriver.php and add the following:

<?php

class LocalValetDriver extends LaravelValetDriver
{
    public function serves($sitePath, $siteName, $uri): bool
    {
        if (str_contains($uri, 'node_modules')) {
            return true;
        }

        return parent::serves($sitePath, $siteName, $uri);
    }

    public function isStaticFile($sitePath, $siteName, $uri): bool|string
    {
        $res = parent::isStaticFile($sitePath, $siteName, $uri);

        if (file_exists($path = $sitePath.$uri)) {
            return $path;
        }

        return $res;
    }
}

That should do it. Valet will now use this driver instead of the default Laravel one. I found this to be a quick, simple solution to solving the FontSource issue and it should fix any other libraries with similar isues. All this is really doing is checking if the string node_modules is in the requested URI and telling Valet that it’s not a static file and it can serve the url.

Scripts

Since you’ll be running serve and build a bunch, you can add those as scripts to our package.json file. Remove all the scripts that are there by default (these are for Laravel Mix which we’re not using anymore) and replace with two new ones:

{
    "scripts": {
        "dev": "vite serve --host=mysite.test",
        "prod": "vite build"
    }
}

Now when you want to start the dev server, you can just do:

yarn dev

Or if you’re ready to build for production:

yarn prod

Since everything now goes into a build directory, it’s easy to add to your .gitignore file. You shouldn’t be commiting that stuff to your repo.

Laravel and Inertia

If you’re a Laravel Mix person, then one thing that may seem confusing is that assets are now served from two different locations. In development, your assets are served from the resources directory by Vites development server without being bundled. For production, your assets are served from public/build. You may have guessed that this could be a bit of a challenge from a Laravel point of view. However, we have all the tools we need to make this work.

Inertia assumes that your base view file is resources/views/app.blade.php. In this view you’ll need to check when it’s appropriate to load assets from the different locations. In production you can use the contents of the build/manifest.json file to load the bundled assets, while Vite will take care of assets during development. You can make use of @production Blade directive to only target stuff that is meant for production. This means that we can change where assets are loaded from simply by changing the value of APP_ENV:

<!doctype html>
<html>
<head>
    <title>Laravel, Vue, Inertia and Vite</title>
    <meta name="viewport" content="width=device-width;initial-scale=1" />
    <meta charset="UTF-8" />
    @production
        @php
            $manifest = json_decode(file_get_contents(
                public_path('build/manifest.json')
            ));
        @endphp
        <script type="module" src="/build/{$manifest['resources/js/app.js']['file']}"></script>
        <link rel="stylesheet" href="/build/{$manifest['resources/js/app.js']['css'][0]}" />
    @else
        <script type="module" src="http://localhost:3000/@vite/client"></script>
        <script type="module" src="http://localhost:3000/resources/js/app.js"></script>
    @endproduction
</head>
<body>
    @inertia
</body>
</html>

That’s it. You made it. If you run yarn dev and you’re able to get to your app through your normal Valet url, then you got it right. You should also be able to get to it without the dev server running if you first run yarn prod.

Vite has quickly become a big part of the tools I use daily. Let me know if you find all this useful.

One last stab at some self promotion

You don’t have to do this yourself every single time because I did it for you. I have a simple Laravel package called Vitamin which you can find here. It’s this simple:

composer require thepublicgood/vitamin

php ./artisan vitamin:init

It’s early days, so let me know if you find it useful.

Subscribe to THEPUBLICGOOD Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe