Fixed: This Page Has Expired on Livewire After npm Update

The problem: After running npm update, submitting any Livewire form throws Laravel's "This page has expired" screen instead of updating the component — even on a session that's only a few minutes old.

Environment: Laravel 11.x, Livewire 3.x (composer package), livewire/alpinejs frontend assets pulled via npm, session driver: database.

The Error

▲ storage/logs/laravel.log
Illuminate\Session\TokenMismatchException: CSRF token mismatch.

  at vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:68
  in App\Http\Middleware\VerifyCsrfToken

// no Livewire-specific exception here — this is Laravel's normal
// CSRF check, meaning the request never made it through Livewire's
// usual AJAX pipeline at all; it fell back to a full page POST

Why It Happens

The important clue is that this is a plain Laravel CSRF exception, not a Livewire-specific one. Livewire components normally update over an AJAX request carrying their own signed "snapshot" (the component's serialized state), not a full page reload. If that AJAX request never fires — or fires and gets rejected before Livewire's own handling kicks in — the browser can fall back to submitting the form as a regular POST, which hits standard session/CSRF middleware and expires the moment the session token is stale.

npm update is the trigger here because Livewire ships two halves that must stay in lockstep:

  1. The composer package (livewire/livewire) — the PHP half, generates and validates component snapshots.
  2. The npm-installed JS assets (Alpine.js and Livewire's own JS runtime, whether bundled via Vite or pulled from a package) — the browser half, that actually sends the AJAX requests.

An unpinned npm update can bump Alpine.js or a Livewire-related JS dependency to a newer minor version that expects a slightly different request/response shape than your installed composer version generates. When the JS and PHP halves disagree, the AJAX call silently fails or the JS runtime never properly boots the component — and some browsers/forms fall back to a native submit, landing you on the CSRF page with an otherwise normal session.

The Fix

1. Confirm the mismatch first — open browser devtools, submit the form, and check the Network tab. If you see a normal (non-XHR) POST request to the page URL instead of a request to /livewire/update, Livewire's JS never intercepted the submit.

2. Pin your Livewire-related JS dependencies instead of leaving them on floating semver ranges, so npm update can't silently jump a minor version:

"alpinejs": "^3.13.0"   // caret allows any 3.x — can jump on update
"alpinejs": "3.13.0"    // exact version — only changes when you choose

3. Rebuild and clear compiled/cached views so the browser isn't serving a mix of old cached JS and new PHP-generated snapshots:

$ php artisan view:clear
$ npm run build

4. Hard-refresh in the browser (not just a normal reload) after deploying the fix — a service worker or aggressively cached JS bundle from before the update can keep running the old, incompatible frontend code even after your server-side assets are correct.

Step-by-Step

  1. Open the Network tab, resubmit the failing form, and confirm whether the request goes to /livewire/update (AJAX, expected) or the current page URL (full POST, the bug).
  2. Check package.json for any Livewire-adjacent packages (alpinejs, @livewire/*) using caret or tilde ranges, and pin them to exact versions.
  3. Run npm install to lock in the pinned versions, then npm run build.
  4. Clear Laravel's view cache with php artisan view:clear so no stale compiled Blade references an old asset path.
  5. Hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R) to bypass any cached JS bundle from before the fix.
  6. Resubmit the form and confirm the Network tab now shows the request going to /livewire/update and the page updates without a full reload.
⚠ Edge case: if pinning versions doesn't fix it and the Network tab shows the request correctly hitting /livewire/update but still failing, the cause is likely unrelated to the JS/PHP version mismatch — check whether APP_KEY changed recently (it encrypts Livewire's snapshot data, so a key rotation invalidates every in-flight component) or whether your session driver's storage (database table, Redis) was cleared or migrated around the same time as the npm update, which is an easy coincidence to misattribute to the update itself.

Still getting the expired page after all of this? Share your package.json and Network tab screenshot on the Contact page and I'll help dig further.

Advertisement
Advertisement