Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

Making Vite and Valet play nice together

Original – by Freek Van der Herten – 3 minute read

Yesterday, the Laravel team launched the official vite-plugin. From now on, Vite will be the standard build tool for Laravel. The main benefits are vastly improved build times and a more straightforward API. Want to know more about it? Head over to the official docs. There's also a migration guide to go from Mix to Vite.

When I followed that guide to upgrade the freek.dev codebase from Mix to Vite , npm dev could successfully start-up Vite, but in the browser the JS / CSS did not load. Let's review how I could fix that.

For local development, I rely on Valet to run sites. For each site, I usually run valet secure so the site loads over HTTPS. And that is what is causing problems. By default, Vite will use the https://localhost:3000 domain to serve assets. But by default, your browser doesn't trust HTTPS from localhost as there is no matching certificate.

Tim MacDonald was so friendly to point me to the official docs that contain a solution. You have to add this bit of configuration to your Vite config file.

export default defineConfig({
    // ...
    server: { 
        https: true, 
        host: 'localhost', 
   }, 
});

And then, you need to go to https://localhost:3000 in your browser. You'll get a warning that there is no certificate, and you have to accept the security warning there.

That will work, but there's another way which doesn't involve having to click away from a security warning. You can configure Vite in such a way that it will use the certificates that Valet generates.

Here's what the vite.config.js file looks like for freek.dev.

import fs from 'fs';
import laravel from 'laravel-vite-plugin'
import {defineConfig} from 'vite'
import {homedir} from 'os'
import {resolve} from 'path'

let host = 'freek.dev.test'

export default defineConfig({
    plugins: [
        laravel([
            'resources/js/app.js',
        ]),
    ],
    server: detectServerConfig(host),
})

function detectServerConfig(host) {
    let keyPath = resolve(homedir(), `.config/valet/Certificates/${host}.key`)
    let certificatePath = resolve(homedir(), `.config/valet/Certificates/${host}.crt`)

    if (!fs.existsSync(keyPath)) {
        return {}
    }

    if (!fs.existsSync(certificatePath)) {
        return {}
    }

    return {
        hmr: {host},
        host,
        https: {
            key: fs.readFileSync(keyPath),
            cert: fs.readFileSync(certificatePath),
        },
    }
}

This code will check if the certificates generated by Valet exist, and if they do, use them. Those certificates do not exist in production, and the default server configuration will be used.

This is slightly more complex than the official suggestion solution, but it has the benefit that you don't have to click away from that security warning. I can promise you that on a new computer, you'll forget that you have to do that and lose some time.

The Vite and vite-plugin themselves are great, by the way. It results in dramatically increased build times for my projects.

Want some more Vite goodness? Check out my other blog post on how to configure Vite to automatically reload your browser when you save a Blade file.

There are probably more ways to fix this problem. Let me (and my other readers) know your solution in the comments below.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Julien Casanova avatar

Thx so much Freek, just installed a new laravel app, and this Vite stuff was driving me nuts ! You can go on vacation and relax now ;)

Ricardo Valenzuela avatar

It took me a while to get vite working with laravel homestead on windows. A part of the code you posted helped me a lot.

I will share all my current vite configuration for laravel homestead in windows environment.

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

let host = 'localhost'

export default defineConfig({
    server: {
        hmr: { host },
        host,
    },
    plugins: [
        laravel({
            input: 'resources/js/app.js',
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
        {
            name: 'blade',
            handleHotUpdate({ file, server }) {
                if (file.endsWith('.blade.php')) {
                    server.ws.send({
                        type: 'full-reload',
                        path: '*',
                    })
                }
            }
        }
    ],
});
Hardik Shah avatar

My setup still doesn't work with latest laravel :|

import fs from 'fs';
import laravel from 'laravel-vite-plugin'
import {defineConfig} from 'vite'
import {homedir} from 'os'
import {resolve} from 'path'

let host = 'saas-scrartch.test'

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

function detectServerConfig(host) {
    let keyPath = resolve(homedir(), `.config/valet/Certificates/${host}.key`)
    let certificatePath = resolve(homedir(), `.config/valet/Certificates/${host}.crt`)

    if (!fs.existsSync(keyPath)) {
        return {}
    }

    if (!fs.existsSync(certificatePath)) {
        return {}
    }

    return {
        hmr: {host},
        host,
        https: {
            key: fs.readFileSync(keyPath),
            cert: fs.readFileSync(certificatePath),
        },
    }
}

Am I doing anything wrong? Please help

Freerk van Zeijl avatar

Please take a look at my last reply on this post. That should work with latest laravel-version.

common99 avatar

If you can do without the hot reload feature, doing an

npm run build

is a much simpler solution

Freerk van Zeijl avatar

To use APP_URL from .env-file as host to prevent setting up the host in multiple files you can use vites loadEnv-method

import { defineConfig, loadEnv } from 'vite'
import laravel from 'laravel-vite-plugin'
import fs from 'fs'
import { resolve } from 'path'
import { homedir } from 'os'

export default defineConfig(({ command, mode }) => {
  // Load current .env-file
  const env = loadEnv(mode, process.cwd(), '')

  // Set the host based on APP_URL
  let host = new URL(env.APP_URL).host
  let homeDir = homedir()
  let serverConfig = {}

  if (homeDir) {
    serverConfig = {
      https: {
        key: fs.readFileSync(
          resolve(homeDir, `.config/valet/Certificates/${host}.key`),
        ),
        cert: fs.readFileSync(
          resolve(homeDir, `.config/valet/Certificates/${host}.crt`),
        ),
      },
      hmr: {
        host
      },
      host
    }
  }

  return {
    plugins: [laravel(['resources/js/app.js'])],
    server: serverConfig
  }
});
Sam Leicester avatar

This has been incorporated in the Laravel Vite plugin via a valetTls option, see the documentation: Working With A Secure Development Server

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
 
export default defineConfig({
    plugins: [
        laravel({
            // ...
            valetTls: 'my-app.test', 
        }),
    ],
});
Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.