When your Laravel site works locally and ships to production with a blank page, and view-source shows every stylesheet and script pointing at http://localhost:5173, you have exactly one problem: a file named hot sitting in your public/ directory. Not a config issue, not an .env issue, not a Vite plugin issue. One leftover file. I know because I shipped it to my own production server once, and the fix I built afterward now runs automatically on every deploy of mejba.me.
Here is the two-minute fix, why it happens, and the pipeline change that makes it impossible to repeat.

The two-minute fix
SSH into your production server and run:
cd /path/to/your/app
rm -f public/hot
php artisan optimize:clear
Reload the site. If public/build/manifest.json exists, your asset URLs immediately switch to hashed files under /build/assets/. If the manifest is missing too, build locally and get it up there:
# on your machine
npm run build
# then deploy, or upload public/build/ to the server
That is genuinely the whole repair. The rest of this post is about making sure you never do it a second time.
Why a single file hijacks every asset URL
When you run npm run dev, Laravel's Vite plugin writes a public/hot file containing the dev server URL. The @vite Blade directive checks for that file on every request: if it exists, all asset tags point at the hot-module-replacement server (localhost:5173); if it does not, they point at the compiled, hashed files listed in public/build/manifest.json.
That check has no environment awareness. APP_ENV=production does not disable it. The file is the switch. So the bug reaches production through exactly two doors:
- The file gets committed.
public/hotis not in every starter.gitignore, and one carelessgit add .whilenpm run devis running puts it in the repo forever. - Someone runs
npm run devon the server. Usually while debugging, usually at the worst possible time.
The failure is invisible locally because on your machine localhost:5173 actually answers. Only your visitors get the broken version. That asymmetry is why teams hit this repeatedly: nothing in development ever looks wrong.
The permanent fix lives in your deploy pipeline
After getting bitten, I made three changes to my own project, and all three are still in the repo today.
First, .gitignore blocks the file at the source:
/public/hot
# /public/build is committed because we build locally (no npm on production)
Note the second line. My production server has no Node, so compiled assets travel through git. If your server does run npm ci && npm run build during deploys, keep public/build ignored instead; either policy works as long as you pick one deliberately.
Second, the deploy workflow deletes hot unconditionally. My GitHub Actions post-deploy script contains this line, and it has run on every single deploy since:
# CRITICAL: Remove hot file to prevent localhost:5173 issue
rm -f public/hot
rm -f is idempotent. Ninety-nine deploys out of a hundred it deletes nothing. The hundredth time, it saves the site. The full pipeline around that line is in my guide to deploying Laravel automatically with GitHub Actions.
Third, the project docs say so. My repo's instructions file for both humans and AI agents carries a one-line warning: the deploy always removes public/hot, never commit this file. That matters more than it sounds. Documentation next to the code is what stops a future contributor, or a code-generation agent, from "helpfully" committing the file back.
If you take one structural idea from this post, take that trio: ignore the file, delete it on deploy, write the rule down. Each layer catches a different way the bug re-enters.
Verify you are actually fixed
Two checks, thirty seconds:
- View source on the production page. Every
<link>and<script>for your app assets should referencehttps://yourdomain.com/build/assets/app-<hash>.cssstyle URLs. Any mention of5173means a cached HTML copy is still circulating; clear your application cache and any CDN or proxy cache in front of the site. - Confirm the manifest matches the files.
public/build/manifest.jsonmaps source files to hashed filenames. If you upload a new manifest without its matching hashed files (or vice versa), you trade a blank page for a half-styled one. Always shippublic/buildas a unit from a singlenpm run build.
If you deploy with a script, add a hard assertion so a bad state fails the deploy instead of reaching users:
test -f public/build/manifest.json || { echo "Vite manifest missing"; exit 1; }
test ! -f public/hot || { echo "hot file present"; exit 1; }
Two lines of CI beat any amount of post-incident debugging.
Quick answers
Does this happen on Forge, Vapor, or Docker too? The mechanism is identical everywhere: the hot file wins wherever it exists. Managed platforms usually run a clean build per release, which masks the problem until someone runs npm run dev in the wrong shell.
Should I ever run npm run dev in production? No. dev starts an HMR server for your local browser. Production only ever needs the output of npm run build.
Why did it break only after my latest deploy? Almost always: the hot file just got committed for the first time, or your deploy started syncing files it previously skipped. Check git log --oneline -- public/hot to find the commit that introduced it.
While you are hardening the deploy path, two related tune-ups are worth the same visit: my checklist for running Laravel well on shared hosting and, if your pages are template-heavy, a look at what Livewire Blaze actually speeds up.
Stuck on a deploy that still misbehaves?
If your production site is loading dev-server assets even after removing hot, something in your pipeline is regenerating or re-uploading it, and finding that step is a fifteen-minute job with fresh eyes. I debug broken Laravel deployments regularly, and untangling one is usually faster than living with it. Get in touch and tell me what your view-source shows.