Hardening the Livewire Starter Kit
We took the official laravel/livewire-starter-kit (Livewire v4 + Flux + Alpine), ran it inside Docker, and published a production-hardened fork. Tests go from the upstream 33 passed to 37 passed / 92 assertions — re-run in Docker on 2026-0…
Verification environment
- PHP 8.5.5
- Laravel 13.x
- Composer 2.9.7
- Node 22.22.2
- npm 10.9.7
- Frontend Livewire v4 + Flux + Alpine
- Database SQLite (tests)
- OS Docker Desktop (php:8.5-cli-bookworm)
We took the official laravel/livewire-starter-kit (Livewire v4 + Flux + Alpine), ran it inside Docker, and published a production-hardened fork. Tests go from the upstream 33 passed to 37 passed / 92 assertions — re-run in Docker on 2026-07-23 and still matching. This article covers the Livewire-specific findings — of the three starter kits, Livewire has the largest non-backend delta.
The 6 shared backend improvements are consolidated in the pillar:
→ Hardening the shared Laravel starter-kit backend
Target
| Item | Value |
|---|---|
| Name | Laravel + Livewire Starter Kit |
| Official URL | https://github.com/laravel/livewire-starter-kit |
| Improved fork | https://github.com/codelift-dev/livewire-starter-kit/tree/improvements |
| Stack | Laravel 13 + Livewire v4 + Flux UI + Alpine + Fortify |
| Upstream / improved license | MIT / MIT |
| Verification date | 2026-04-19 |
| Upstream commit | laravel/livewire-starter-kit@62c60c8 |
Upstream verification result
docker compose run --rm app returns 33 passed (77 assertions). Fewer tests than the React / Vue kits' 40 — this variant exercises form behavior through Livewire's component test harness rather than the Inertia HTTP harness, so the assertion distribution differs. Coverage is not weaker.
Four ways Livewire is structurally different
The Laravel backend is shared across all three kits, but the Livewire variant differs in ways that change the finding set.
| Item | React / Vue | Livewire |
|---|---|---|
@laravel/vite-plugin-wayfinder |
Yes (shells out to artisan at build) | No |
| Inertia | Yes (emits initial props as a JSON data block) | No |
Default middleware in bootstrap/app.php |
3 Inertia-related appended | Empty (callback body is just //) |
| Settings routes | PATCH/DELETE HTTP verbs + Controller | Route::livewire(...) + internal AJAX actions |
These four produce the Livewire-specific differences below.
Re-verified 2026-07-23: all four were re-checked against upstream's current HEAD (
laravel/livewire-starter-kit@main).withMiddleware()inbootstrap/app.phpstill contains only//,vite.config.js(not.ts) still has no wayfinder entry, and settings still route throughRoute::livewire(...). None of the four has changed.
The Wayfinder footgun doesn't happen
The "npm run build before composer install fails opaquely" problem that hits React / Vue does not occur in the Livewire kit. Its vite.config.js (React/Vue use .ts) has no @laravel/vite-plugin-wayfinder, so the build never shells out to artisan and has no dependency on vendor/autoload.php.
The official README lacking a Setup section is a minor gap shared by all three kits, so the fork adds one for contributors — but without the React/Vue-specific "looks like a Wayfinder bug" trap.
Security-header middleware — a worse starting state than React/Vue
Pillar finding D (security headers) is shared across all three, but Livewire's starting state is the worst. The withMiddleware() callback in bootstrap/app.php has a body of just a // comment — zero appended web middleware. React / Vue at least had the three Inertia-related ones; Livewire has none.
The fork's SetSecurityHeaders middleware becomes the first web middleware appended in this fork.
CSP — what needs a nonce differs from React / Vue
Livewire does not embed initial state in an inline script. The client JS loads via external <script src>, component state lives in wire:* attributes on HTML elements, and updates go through a fetch to /livewire/update.
That means it moves cleanly to a nonce-based CSP with no 'unsafe-inline'. What takes the nonce here is the bootstrap script Livewire and Flux emit.
Correction (2026-07-23): this section originally called that Livewire's biggest specific, and said the Inertia kits embed initial state in an inline
<script>so dropping'unsafe-inline'required patching Inertia. The second half was wrong. Inertia emits atype="application/json"data block, which the browser never executes andscript-srctherefore never covers, so no Inertia patch is needed (verification).So going nonce-based is not itself a Livewire specific — all three kits can. What differs is what needs the nonce: on the Inertia kits it is the starter kit's own dark-mode inline script.
This hardening article keeps the CSP policy aligned across all three kits (conservatively keeping 'unsafe-inline'). The nonce migration is its own follow-up article:
→ Laravel + Livewire Starter Kit: nonce-based CSP
J. Settings rate limiting — deferred because the shape differs
In React / Vue, throttling profile update/destroy was just adding throttle middleware to the PATCH/DELETE routes in routes/settings.php.
Livewire is different. routes/settings.php uses Route::livewire('settings/profile', ...) to render component pages, and profile updates and password changes flow through Livewire's internal AJAX endpoint (/livewire/update). A route-level throttle only covers the initial page load, not component actions.
The correct fix is per-component RateLimiter::tooManyAttempts inside the action methods — a substantively different commit from React/Vue. It's deferred this pass for a Livewire-specific follow-up.
Commit layout — 7 commits
| Commit | Finding | Source |
|---|---|---|
| README Setup | A (weakened — no wayfinder) | this article |
.env.example comments |
B | pillar |
| timezone via env | C | pillar |
URL::forceScheme |
E | pillar |
SetSecurityHeaders middleware + test |
D | pillar |
| layered login rate limiter | G | pillar |
auth log channel + subscriber + test |
I | pillar |
React/Vue have 8 commits, Livewire has 7. The difference is finding J — deferred here because the fix shape differs.
Tests: upstream 33 passed → improved 37 passed / 92 assertions.
Before / after (Livewire-specific part)
| Dimension | Official | Improved |
|---|---|---|
php artisan test |
33 passed / 77 assertions | 37 passed / 92 assertions |
| README Setup section | None | Added |
| Initial middleware stack | Empty | SetSecurityHeaders appended |
| Reachable CSP strictness | — | Nonce-based possible (see follow-up) |
The before/after for the 6 shared backend findings is in the pillar.
When this fits
- You're starting a production product on
laravel/livewire-starter-kit. - You want PHP / Blade all the way down.
- You want a strict CSP — Livewire can drop
'unsafe-inline'via the nonce migration (an edge over the Inertia kits).
Reproduce and adopt
git clone https://github.com/codelift-dev/livewire-starter-kit.git
cd livewire-starter-kit
git checkout improvements
docker compose -f codelift/docker-compose.yml build
docker compose -f codelift/docker-compose.yml run --rm app
Application diff only: git diff origin/main improvements -- . ':!codelift'
Related
- Pillar: Hardening the shared Laravel starter-kit backend
- Follow-up: Livewire Starter Kit: nonce-based CSP
- Sister cluster: the Inertia kits (React and Vue)
- Comparison: React vs Vue vs Livewire
License
Upstream and improved fork both MIT. Findings reflect the verification date.
Featured in comparisons
Related articles
- Hardening the shared Laravel starter-kit backend Laravel's official starter kits (React / Vue / Livewire) differ in their frontend layer, but they share the same Laravel + Fortify backend code. So most of the production-hardening work is identical across all three. This is the pillar art…
- Hardening the Inertia Starter Kits We ran laravel/react-starter-kit and laravel/vue-starter-kit in Docker and published two forks rewritten to survive production. Tests went from 40 passed to 44 passed / 151 assertions in both.
- Laravel + Livewire Starter Kit: nonce-based CSP The SetSecurityHeaders middleware shipped in our Livewire Starter Kit Docker-verified fork kept 'unsafe-inline' in script-src and style-src. That was a deliberate placeholder to match the React/Vue forks; the Livewire architecture doesn't …