← All writing
 ·  1 min read

Fixing Safari Mixed Content Issues with Vite and mkcert

Safari blocks Vite’s hot module reload (HMR) when your Laravel app runs over HTTPS but Vite serves plain HTTP. Here’s how I fixed it with mkcert, a separate dev-only Vite config, and one extra package.json script.

Fixing Safari Mixed Content Issues with Vite and mkcert

If you’ve ever tried running a Laravel + Vite + Inertia app in Safari and hit this lovely error:

text
[blocked] The page at https://your-app.test requested insecure content from http://[::1]:5173/@vite/client. This content was blocked and must be served over HTTPS.

…you’re not alone. Safari is stricter about mixed content, so when your app runs on https:// and Vite’s dev server injects the Hot Module Replacement (HMR) client from plain http://, it gets blocked. Fonts may still load, but hot reloading breaks and you’ll waste time refreshing pages. Or, as I've run into, the page does not load and you get a white browser screen.

The Fix: mkcert + a Dev-Only Vite Config

1. Generate trusted local certificates

First, install mkcert to create TLS certs that Safari (and Chrome/Firefox) will trust on your machine.

bash
brew install mkcertmkcert -install

Then make a folder for your dev certs and generate one that covers localhost, 127.0.0.1, and ::1:

bash
mkdir -p ~/.config/dev-certs
mkcert -key-file ~/.config/dev-certs/localhost-key.pem \
       -cert-file ~/.config/dev-certs/localhost-cert.pem \
       localhost 127.0.0.1 ::1

2. Create a separate Vite config for dev

Instead of polluting your production vite.config.js, create a vite.dev.config.js:

javascript
// vite.dev.config.js
import baseConfig from './vite.config.js'
import fs from 'fs'
import path from 'path'

export default {
    ...baseConfig,
    server: {
        host: 'localhost',
        port: 5173,
        https: {
            key:  fs.readFileSync(path.resolve(process.env.HOME, '.config/dev-certs/localhost-key.pem')),
            cert: fs.readFileSync(path.resolve(process.env.HOME, '.config/dev-certs/localhost-cert.pem')),
        },
        hmr: {
            protocol: 'wss',
            host: 'localhost',
            port: 5173,
        },
    },
}

This extends your normal config but forces HTTPS + WSS for HMR.

3. Add a script to package.json

Finally, update your scripts so you have two dev modes:

json
"scripts": {
    "dev": "vite",
    "dev:https": "VITE_DEV_SERVER_URL=https://localhost:5173 vite --config vite.dev.config.js",
    "build": "vite build && vite build --ssr"
}

Now you can run:

  • npm run dev: plain Vite (for teammates, no TLS required).
  • npm run dev:https: Safari-friendly dev server over HTTPS.

With one-time mkcert setup and a small config tweak, you get fast hot reloading on Safari with zero mixed-content errors. Your teammates can still use npm run dev if they don’t care, and you can stick with npm run dev:https when testing in Safari.