DEV Community

Cover image for Fixing CSS not updating in production (Laravel + Blade)
Tahsin Abrar
Tahsin Abrar

Posted on

Fixing CSS not updating in production (Laravel + Blade)

If you change a CSS file but users (often on mobile) still see the old styles, it’s almost always a caching problem. This post shows simple, practical fixes for Laravel/Blade projects from quick hacks to proper production setups with real examples you can copy.


The problem short story

Last month I deployed a tiny CSS change: a button color and spacing. On my desktop it looked fine. But several users (mostly on mobile) complained the site still showed the old look. I ran php artisan optimize:clear, redeployed, even asked one user to refresh nothing. The cause? Browser (or CDN) still serving the old static file from cache.

Browsers, CDNs, and sometimes reverse proxies hold static files (CSS/JS) for speed. If the filename or URL does not change, they assume it’s the same file and keep using the cached copy.


Why php artisan optimize:clear didn’t fix it

php artisan optimize:clear clears Laravel’s server-side caches (views, routes, configs). It does not change the static asset file name or force browsers/CDNs to fetch a new style.css. So browser cache still wins.


Practical fixes (from quick to best practice)

1) Best: Use versioned assets (Vite or Mix)

If you use Vite (Laravel 9+ default) or Laravel Mix, they produce versioned/hashed builds so each build has new filenames.

Vite (Blade):

@vite(['resources/css/app.css', 'resources/js/app.js'])
Enter fullscreen mode Exit fullscreen mode

Build for production:

npm run build
Enter fullscreen mode Exit fullscreen mode

Laravel Mix (webpack.mix.js):

mix
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css')
  .version(); // <-- generate hashed filenames
Enter fullscreen mode Exit fullscreen mode

Then in Blade:

<link rel="stylesheet" href="{{ mix('css/app.css') }}">
Enter fullscreen mode Exit fullscreen mode

Build for production:

npm run prod
Enter fullscreen mode Exit fullscreen mode

Why this works: the filenames include a hash (e.g. app.abc123.css) so when content changes, filename changes, forcing browsers & CDNs to fetch the new file.


2) Good quick fix: Use file modification time (filemtime) better than time()

If you don't use Mix/Vite yet, avoid ?v={{ time() }}. time() changes every request and breaks caching (bad for performance). Use the file’s last-modified timestamp so version only changes when file changes:

<link rel="stylesheet" href="{{ asset('css/style.css') }}?v={{ filemtime(public_path('css/style.css')) }}">
Enter fullscreen mode Exit fullscreen mode

This way the query string changes only when the file actually changes.


3) Quick (but temporary) hack: ?v={{ time() }}

This forces a fresh download every time. It fixes the "old CSS" problem immediately but kills browser caching and hurts load time. Use only for debugging or one-time hotfix.

<link rel="stylesheet" href="{{ asset('css/style.css') }}?v={{ time() }}">
Enter fullscreen mode Exit fullscreen mode

4) CDN / Cloudflare purge cache or configure properly

If you use Cloudflare or any CDN, it may serve cached CSS even after you update files.

  • For a quick fix: Purge Cache → Purge Everything (Cloudflare) or purge specific URLs.
  • Better: configure your CDN to respect file-hash/versioning. If you use hashed filenames, leave CDN cache long (1 year) safe because filename changes with content.

5) Set proper headers (server-side)

For static assets, set far-future Cache-Control and use hashed filenames. Example nginx snippet:

location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg)$ {
    expires 365d;
    add_header Cache-Control "public, max-age=31536000, immutable";
}
Enter fullscreen mode Exit fullscreen mode

immutable means the browser can trust the cached file until the URL changes.


6) Laravel cache commands (still useful)

These clear Laravel caches (won’t force browser to fetch new CSS but good for general deploy):

php artisan view:clear
php artisan route:clear
php artisan config:clear
php artisan cache:clear
php artisan optimize:clear
Enter fullscreen mode Exit fullscreen mode

How I fixed my real issue (short walkthrough)

  1. I had linked public/css/style.css directly.
  2. I built assets with Mix and enabled mix.version() in webpack.mix.js.
  3. Replaced <link href="{{ asset('css/style.css') }}"> with <link href="{{ mix('css/style.css') }}"> in Blade.
  4. Ran npm run prod, deployed new public/mix-manifest.json and hashed files.
  5. Purged Cloudflare cache for the CSS URL.
  6. Tested on mobile (incognito) new styles were visible immediately.

That flow solved the problem forever no more users complaining.

Top comments (0)