skip to content

After a florist's shop Laravel app deploys a new Vite build, some visitors get old CSS and others get 404s on /build/assets files; how do you diagnose it?

level: seniorimportance: should knowfreq 38%

answer

  1. hashed names rule out browser cache
  2. HTML and manifest from different builds
  3. stale public/hot on a server
  4. long-lived workers cache the manifest
  5. old hashed files vanish too early

basics

~20 s

Hashed file names mean the browser cache is rarely at fault; the HTML and the files on disk disagree. Check for a stale public/hot, a manifest cached by long-lived workers, cached HTML pointing at deleted hashes, and servers or a CDN missing the new build.

solid answer

~50 s

Because every build gives changed files new content-hashed names, "old CSS" means some HTML still references the old hashed names, and a 404 means the HTML references names the server no longer (or not yet) has. I would view source on an affected page and compare the `/build/assets` names with `public/build/manifest.json` on each web node. Typical causes: a `public/hot` file copied or left on a server (tags point at a dev server); Octane workers still holding the old manifest in the `Vite` class's static cache; full-page or CDN-cached HTML pointing at hashes the new release deleted; a multi-server deploy where only one node built; or `ASSET_URL` pointing at a CDN that received files after the app switched. The fix is a release that builds before it goes live, keeps the previous build's files for a grace period, and reloads long-lived workers.

code

bash · 8 lines
bash
# on each web node: which hashed CSS does the manifest name?
grep -o '"file": "assets/app-[^"]*\.css"' public/build/manifest.json

# a production box must never have this file
ls -l public/hot

# build in the release directory before it goes live
npm ci && npm run build

go deeper

for a junior

Know that each build renames changed files, so the page HTML must come from the same build as the files on disk.

for a middle

Read the HTML and the manifest side by side and name the mismatch: stale hot file, stale manifest or missing build.

for a senior

Show the release design: build before the switch, identical artifacts on every node, a grace period for old hashes, worker reloads, cache purges.

for a principal

Decide where assets live across releases, on the app servers or a CDN that retains builds, and set how long old builds stay reachable.

## Start from what hashing guarantees laravel-vite-plugin builds every entry into `public/build/assets` with a **content hash** in the file name and records the mapping in `public/build/manifest.json`. `@vite` resolves names from that manifest at render time. Two consequences shape the diagnosis: - A **changed** stylesheet gets a **new URL**. A browser holding the old file in cache will not reuse it for the new URL, so "tell customers to hard-refresh" is almost never the fix. - "Old CSS" therefore means **some HTML still names the old hash**, and a 404 means the HTML names a file **that is not on the server** answering the asset request. So the question is always: *which HTML, rendered from which manifest, requested from which server?* ## Diagnose from the page outward 1. **View source on a bad page.** Note the `/build/assets/app-<hash>.css` name, or whether tags point at a dev-server address such as `http://[::1]:5173`. 2. **Compare with each web node's manifest.** `cat public/build/manifest.json` on every server; the entry's `file` should match what the page printed. 3. **Check the response headers** of the HTML: was it served from a full-page cache, a reverse proxy or a CDN rather than rendered just now? 4. **Request the asset URL directly** against each node and against the CDN host, if `ASSET_URL` is set. ## The usual causes | Symptom in the HTML | Cause | Fix | |---|---|---| | tags point at a dev server | a `public/hot` file was copied up or left by a dev server killed without its exit handler | delete it; keep `/public/hot` out of deploy artifacts | | old hash, and the old file still exists | long-lived workers kept the old manifest: the `Vite` class caches the decoded manifest in a static array per process | reload Octane or other long-lived workers after switching | | old hash, and the old file is gone (404) | HTML cached by a full-page cache or CDN outlived the old build | purge HTML caches on release, or keep old assets for a grace period | | new hash, 404 on one node only | multi-server deploy where only one node ran `npm run build`, or a build artifact that was not synced | build once in CI and ship the same `public/build` to every node | | new hash, 404 on the CDN host | `ASSET_URL` points at a CDN that got the files after the app went live | upload assets before switching the app | | page throws `ViteManifestNotFoundException` | the release never built, or `public/build` was excluded (it is gitignored) | add the build step to the pipeline | ## Designing the release so it cannot happen - **Build before going live.** Run `npm ci && npm run build` in CI or in the new release directory, so the manifest and the code that reads it switch together. - **Never run the dev server on a server.** The plugin refuses to when `LARAVEL_FORGE`, `LARAVEL_VAPOR`, `LARAVEL_ENVOYER` or `CI` is set, precisely because a live `public/hot` breaks production. - **Keep the previous build's hashed files for a while.** Visitors with a page open, and HTML still in caches, will request the old names for minutes or hours. Serving assets from a CDN or a shared directory that accumulates builds avoids 404s; laravel-vite-plugin's `clean-orphaned-assets` script can prune files that are not in the current manifest once the grace period is over. - **Reload long-lived workers** after the switch so their static manifest cache is rebuilt. - **Remember what does not help**: `php artisan view:cache` compiles `@vite` into a runtime call, so compiled views never freeze asset URLs, and clearing views does not refresh them. ## For the florist's shop specifically Suppose Valentine's week styling ships and half the customers still see last season's colours. If the site runs under Octane, the first suspect is workers holding the old manifest: while the old build's files are still on disk there is no 404, just stale styles, until the workers reload. If the shop's product pages sit in a full-page cache, the 404s come from cached HTML naming files the new release removed. Both are fixed in the release process, not in the browser.

  • Why does running php artisan view:clear not fix stale asset URLs?
    The Blade compiler turns `@vite(...)` into a PHP call on the `Vite` singleton, so compiled views contain no file names. The hashed URLs are resolved from the manifest on every render, so the fix is to change what the renderer reads (the manifest, or a worker's cached copy), not the compiled views.
  • An Inertia front end code-splits every page. How can Vite::prefetch reduce the pain of navigation after a release?
    `Vite::prefetch()` in a service provider makes `@vite` print a small script that, after the `load` event, adds `rel="prefetch"` links for the entry's dynamic imports — all at once by default, or a few at a time with `Vite::prefetch(concurrency: 3)`. Chunks are fetched while the current build's files still exist, so later navigations do not wait on them.

saying these in an interview costs you the question

  • Visitors just need to clear their browser cache after each deploy
  • Adding a ?v= query string to @vite output fixes stale CSS
  • php artisan view:cache freezes asset URLs into compiled views
  • Deleting all old files in public/build at release time is always safe
  • Running npm run dev on the server is fine if you stop it afterwards