{} CodeLift

Vite Manifest Not Found: Four Different Causes

When Vite manifest not found appears right after a deploy, the answer you find is almost always "run npm run build". Measuring six cases in Docker shows that the build fixes one of the four causes, and that two of them never produce an err…

Pub 2026-08-02 Verified 2026-08-02 Upd 2026-08-12

Verification environment

  • Laravel 13.23.0
  • PHP 8.4.24
  • Node 22.23.2
  • Cases reproduced 6 — two of them return HTTP 200
  • Key finding npm run build does not remove public/hot
  • OS Docker Desktop (php:8.4-cli-bookworm)

When Vite manifest not found appears right after a deploy, the answer you find is almost always "run npm run build". Measuring six cases in Docker shows that the build fixes one of the four causes, and that two of them never produce an error at all.

Run npm run build.

The awkward part is that two of those four causes throw nothing at all — HTTP 200, empty logs, and a page that is quietly broken.

The build actually runs here; no manifest.json was hand-written to make things look convincing.

Scripts: laravel-error-lab.

Environment

Laravel 13.23.0
PHP 8.4.24
Node 22.23.2
Runtime Docker Desktop / php:8.4-cli-bookworm

Each case changes exactly one thing on disk, starting from a working build.

What the six cases actually do

Case Change HTTP Exception
A (working case) 200
B public/build deleted 500 ViteManifestNotFoundException
C public/hot left behind (build present) 200 none
D public/hot left behind and no build 200 none
E manifest only at public/build/.vite/ 500 ViteManifestNotFoundException
F @vite() names an entry not in the manifest 500 ViteException

npm run build is the answer to B, and only B.

There are two exceptions, and they get conflated

People talk about "the manifest error" as one thing. The class and the message differ.

B / E:

Illuminate\Foundation\ViteManifestNotFoundException
Vite manifest not found at: /app/public/build/manifest.json

F:

Illuminate\Foundation\ViteException
Unable to locate file in Vite manifest: resources/js/does-not-exist.js.

F means the manifest was read fine — the entry you asked for simply is not in it. That happens when the path in @vite() disagrees with input in vite.config.js, and rebuilding does not fix it.

Reading the message to the end distinguishes them, but both start with "Vite manifest", so they collapse into the same search query.

B and E are indistinguishable

In case E the manifest file exists — at public/build/.vite/manifest.json. The error is nevertheless byte-identical to B, still naming public/build/manifest.json.

Laravel looks in exactly one place:

Illuminate/Foundation/Vite.php

 56:  protected $manifestFilename = 'manifest.json';
975:  protected function manifestPath($buildDirectory)
977:      return public_path($buildDirectory.'/'.$this->manifestFilename);

With laravel-vite-plugin the manifest lands at public/build/manifest.json. Plain Vite 5 and later writes to .vite/manifest.json, so dropping the plugin or hand-rolling the Vite config produces this state.

"The file is right there and it says it isn't" — you cannot tell until you look at the actual paths.

ls -la public/build/manifest.json public/build/.vite/manifest.json

The worst case is C, because nothing errors

This is the real point of the article.

If a file called public/hot is present, HTTP 200 comes back and no exception is thrown. The page renders perfectly.

Here is what that page then asks the browser to load:

<script type="module" src="http://127.0.0.1:5173/@vite/client"></script>
<link rel="stylesheet" href="http://127.0.0.1:5173/resources/css/app.css" />
<script type="module" src="http://127.0.0.1:5173/resources/js/app.js"></script>

127.0.0.1:5173 is the Vite dev server. Nothing like that runs in production. Fetching it the way a browser would:

http://127.0.0.1:5173/@vite/client -> connection refused (nothing is listening)

Nothing reaches the server logs. The status is 200. Uptime monitoring sees a healthy site. All that exists is a page with no styling.

Why

Illuminate/Foundation/Vite.php

 239:  return $this->hotFile ?? public_path('/hot');
1223:  return is_file($this->hotFile());

isRunningHot() checks only whether the file exists. Not its contents, not whether a dev server is actually alive.

When it returns true, the manifest is never read at all — which is why case D (hot file present, no build) also returns 200. Whether you have build output stops mattering.

npm run dev creates public/hot. A clean exit removes it; an ungraceful kill leaves it behind, and from there it can be committed and shipped.

npm run build cannot fix this

The standard advice was measured against it:

after 'npm run build' with a hot file present: STILL THERE

The build does not delete public/hot. Rebuilding changes nothing, however many times you do it. What is needed is:

rm public/hot

public/hot also belongs in .gitignore. Laravel's stock file covers it — worth confirming if yours has been edited.

Working out which one you have

Three looks at the filesystem settle it.

# 1. is a hot file present? (if so this is your cause — and there is no error)
ls -la public/hot

# 2. where is the manifest?
ls -la public/build/manifest.json public/build/.vite/manifest.json

# 3. does the manifest contain what @vite() asks for?
cat public/build/manifest.json | head -20
What you see Case Fix
public/hot exists C / D rm public/hot
no public/build/manifest.json B npm run build
manifest only under .vite/ E use laravel-vite-plugin, or align the output path
manifest present, entry missing F make @vite() and vite.config.js input agree

If there is no error but the CSS is missing, start at 1. If you have a 500, read the class name in the message: ViteManifestNotFoundException sends you to 2, ViteException to 3.

Stopping it recurring on deploy

C is a development artifact reaching production, so the deploy can close it off:

# anywhere around the build step in your deploy script
rm -f public/hot

One line, and C and D both become structurally impossible.

FAQ

Q. I ran npm run build and it did not help

public/hot is probably still there. The build does not remove it — measured. Check ls -la public/hot and rm public/hot if it exists.

Note this case does not produce "Vite manifest not found" at all: you get HTTP 200 with the CSS and JS simply never loading.

Q. What is public/hot?

A file npm run dev creates, containing the Vite dev server URL. Laravel checks only whether it exists and, if so, points assets at the dev server instead of reading the manifest (Vite.php:1223). It never verifies the dev server is running.

A clean exit from npm run dev deletes it; a forced kill leaves it.

Q. My manifest.json exists but Laravel says it cannot find it

Laravel reads exactly one path: public/build/manifest.json (Vite.php:977). Plain Vite 5 and later writes .vite/manifest.json, so the two diverge when laravel-vite-plugin is not in use.

Check ls -la public/build/.vite/manifest.json. The error message is identical to the file-is-missing case, so nothing but the paths will tell you.

Q. Is Unable to locate file in Vite manifest the same problem?

No. That is ViteException, and the manifest loaded successfully — the entry named in @vite() is not in it.

Check that the path in @vite(['resources/js/app.js']) matches input in vite.config.js. Rebuilding will not fix it.

Q. Styling broke in production but nothing is erroring

Suspect public/hot. With that file present you get HTTP 200, no exception, and nothing in the server logs. The quickest confirmation is the browser devtools network tab: check whether assets are being requested from localhost:5173.

Reproducing this

git clone https://github.com/codelift-dev/laravel-error-lab
cd laravel-error-lab
docker compose build
docker compose run --rm lab bash vite-manifest.sh          # reproduce all six
docker compose run --rm lab bash vite-manifest-detail.sh   # exact exception text

Everything runs inside Docker. No PHP and no Node on the host.

Verified on Laravel 13.23.0 with Node 22.23.2. The Vite.php line numbers will drift, but two things have held since Laravel 9: isRunningHot() tests only for the hot file's existence, and the manifest path is fixed at public/build/manifest.json.

Related articles