Livewire 3 und 4 — was sich geändert hat
Alles, was sich zwischen Livewire 3 und Livewire 4 geändert hat: die neuen Features, die sich lohnen, jeder Breaking Change mit Vorher/Nachher-Code und eine Checkliste für das Upgrade selbst. Einschließlich der drei Änderungen, die Ihre Testsuite bestehen und die Produktion lahmlegen.
1. Die Kurzfassung
Livewire 4 ist ein großes Release mit einem kleinen Upgrade-Pfad. Das Komponentenmodell hat sich nicht geändert: PHP-Klasse, Blade-Vorlage, Zustand auf dem Server, Markup über die Leitung. Geändert hat sich, wo Komponenten liegen, wie sie geschrieben werden und wie viel einer Seite ein einzelnes Update berührt.
Was Sie gewinnen
- Single-File-Komponenten — Klasse und Markup in einer Datei.
- Islands — Bereiche, die sich unabhängig neu rendern, ohne Aufteilung in Kindkomponenten.
- Slots — Eltern reichen Markup hinein, ausgewertet im Kontext des Elternteils.
- Optimistisches UI —
wire:show,wire:text,wire:bindändern das DOM ohne Round Trip. - Drag and Drop —
wire:sort, ohne externe Bibliothek. - Scoped CSS — Komponentenstile, die nicht auslaufen.
Was Sie erwischt
- Nicht geschlossene
<livewire:…>-Tags rendern lautlos gar nichts. wire:model-Modifikatoren haben ihre Bedeutung geändert — Felder synchronisieren nicht mehr.- Der Update-Endpunkt liegt jetzt unter
/livewire-{hash}/— Infrastrukturregeln brechen. wire:transition-Modifikatoren wurden entfernt.- Volt ist in den Kern gewandert.
2. Gegenüberstellung
| Bereich | Livewire 3 | Livewire 4 |
|---|---|---|
| Komponentendateien | app/Livewire/Counter.php + resources/views/livewire/counter.blade.php |
resources/views/components/⚡counter.blade.php (eine Datei) |
| Make-Befehl | php artisan livewire:make Counter |
php artisan make:livewire counter |
| Routing | Route::get('/x', Dashboard::class) |
Route::livewire('/x', 'pages::dashboard') |
| Partielles Rendern | Aufteilung in Kindkomponenten | @island-Bereiche |
| Slots | Nicht unterstützt | {{ $slot }}, <wire:slot name="…"> |
| Drag and Drop | SortableJS plus Klebecode | wire:sort |
| Sofortiges UI | Alpine.js von Hand | wire:show, wire:text, wire:bind |
| Komponenten-CSS | Globales Stylesheet | Scoped <style> in der Komponente |
| Parallele Aktionen | Nicht verfügbar | #[Async], wire:click.async |
| Update-Endpunkt | /livewire/update |
/livewire-{hash}/update |
| JS-Hooks | Livewire.hook('request'|'commit') |
interceptRequest(), interceptMessage() |
| Volt | Separates Paket | Im Kern enthalten |
| Testing | PHPUnit oder Pest | Pest empfohlen, Tests dürfen neben der Komponente liegen |
3. Das Upgrade Schritt für Schritt
# 1. Abhängigkeit anheben
composer require livewire/livewire:^4.0
# 2. Alle Caches leeren
php artisan optimize:clear
# 3. Testsuite laufen lassen, bevor Sie sonst etwas anfassen
php artisan testLaravel Shift automatisiert einen großen Teil der mechanischen Arbeit. Ob Sie es nutzen oder nicht — arbeiten Sie danach die Checkliste unten durch; genau die lautlosen Fehler erkennt ein Werkzeug am schlechtesten.
Eine Reihenfolge, die meist glattgeht
- Paket aktualisieren und Caches leeren.
- Konfigurations-Umbenennungen beheben (Kapitel 4).
- Routing anpassen (Kapitel 5).
- Codebasis nach nicht geschlossenen Komponenten-Tags durchsuchen (Kapitel 6).
- Jedes
wire:modelmit Modifikator prüfen (Kapitel 7). - Infrastrukturregeln für den neuen Endpunkt anpassen (Kapitel 8).
- Entfernte
wire:transition-Modifikatoren ersetzen (Kapitel 9). - Volt-Komponenten migrieren, falls im Einsatz (Kapitel 12).
- Testsuite erneut laufen lassen und die App im Browser durchklicken.
- Erst danach Islands, Slots und Single-File-Komponenten angehen.
4. Umbenennungen in der Konfiguration
| Livewire 3 | Livewire 4 | Hinweis |
|---|---|---|
layout | component_layout | Nutzt den Namespace layouts:: |
lazy_placeholder | component_placeholder | — |
smart_wire_keys | smart_wire_keys | Standard von false auf true gewechselt |
| — | component_locations | Verzeichnisse, die nach Komponenten durchsucht werden |
| — | component_namespaces | Benannte Wurzeln, etwa pages:: |
| — | make_command | Single-File- oder klassenbasierte Generierung |
| — | csp_safe | Build für strengere CSP |
'component_layout' => 'layouts::app',
'component_placeholder' => 'livewire.placeholder',
'component_locations' => [
'resources/views/components',
'resources/views/livewire',
],
'component_namespaces' => [
'pages' => 'resources/views/pages',
],
// Weiterhin klassenbasierte Komponenten erzeugen
'make_command' => [
'type' => 'class',
],5. Routing
use App\Livewire\Dashboard;
Route::get('/dashboard', Dashboard::class)
->middleware('auth')
->name('dashboard');use App\Livewire\Dashboard;
Route::livewire('/dashboard', Dashboard::class)
->middleware('auth')
->name('dashboard');
// Oder nach Komponentenname
Route::livewire('/dashboard', 'pages::dashboard')
->middleware('auth')
->name('dashboard');grep -rn "Route::get(.*::class" routes/.
6. Komponenten-Tags müssen geschlossen sein
<livewire:user-profile>
<livewire:order-list><livewire:user-profile />
<livewire:order-list /># Komponenten-Tags, die nicht selbstschließend sind
grep -rn "<livewire:[^>]*[^/]>" resources/views/
# Die Direktivenform @livewire() ist nicht betroffen
grep -rn "@livewire(" resources/views/7. wire:model hat sich zweimal geändert
7.1 Modifikatoren steuern die clientseitige Synchronisation
<input wire:model.blur="title">
<select wire:model.change="status"><input wire:model.live.blur="title">
<select wire:model.live.change="status">7.2 Kein Bubbling von Kindelementen mehr
<div wire:model="value">
<input type="text">
</div><div wire:model.deep="value">
<input type="text">
</div># Jedes wire:model mit Modifikator
grep -rn "wire:model\.[a-z]" resources/views/
# wire:model auf einem Nicht-Input-Element ist ein Bubbling-Kandidat
grep -rn "<div[^>]*wire:model" resources/views/8. Der Update-Endpunkt ist umgezogen
Alle Livewire-URLs enthalten jetzt einen Hash: aus /livewire/ wurde
/livewire-{hash}/.
Wo Sie nachsehen sollten
location-Blöcke in nginx oder Apache- WAF- und Firewall-Allowlists
- CDN-Regeln zum Cache-Bypass
- Rate Limiter, die am Pfad hängen
- Routing-Regeln des Load Balancers
- Monitoring- und Uptime-Checks
connect-src-Einträge in der CSP
location /livewire/ {
# …
}location ~ ^/livewire(-[a-z0-9]+)?/ {
# …
}9. wire:transition wurde neu gebaut
<div wire:transition.opacity.duration.300ms>
…
</div><div wire:transition>
…
</div>::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 300ms;
}10. Änderungen in der PHP-API
Streaming
$this->stream(to: '#container', content: 'Hello', replace: true);$this->stream('Hello', replace: true, el: '#container');Komponenten mounten
mount($name, $params = [], $key = null)mount($name, $params = [], $key = null, $slots = [])Neue Attribute
| Attribut | Zweck |
|---|---|
#[Async] | Aktion parallel ausführen |
#[Renderless] | Neurendern nach der Aktion überspringen |
#[Prop] | Eigenschaft als Prop vom Elternteil deklarieren |
#[Json] | Daten direkt an JavaScript zurückgeben |
11. Änderungen in der JavaScript-API
$wire.$js('showToast', { message: 'Gespeichert' });$wire.$js.showToast = { message: 'Gespeichert' };Aus Hooks wurden Interceptors
document.addEventListener('livewire:init', () => {
Livewire.hook('request', ({ options, fail }) => {
options.headers['X-Tenant'] = window.tenantId;
fail(({ status, preventDefault }) => {
if (status === 419) {
preventDefault();
window.location.reload();
}
});
});
});document.addEventListener('livewire:init', () => {
Livewire.interceptRequest(({ options, fail }) => {
options.headers['X-Tenant'] = window.tenantId;
fail(({ status, preventDefault }) => {
if (status === 419) {
preventDefault();
window.location.reload();
}
});
});
Livewire.interceptMessage(({ component, succeed }) => {
succeed(() => {
console.debug('Komponente aktualisiert', component.name);
});
});
});{{-- Validierungsfehler auf dem Client --}}
<div x-show="$errors.has('email')" x-text="$errors.first('email')"></div>12. Volt ist in den Kern gewandert
| Volt | Livewire 4 |
|---|---|
Livewire\Volt\Component | Livewire\Component |
Volt::route() | Route::livewire() |
Volt::test() | Livewire::test() |
| Volt Service Provider | Entfernen |
Paket livewire/volt | Abhängigkeit entfernen |
composer remove livewire/volt<?php
use Livewire\Volt\Component;
new class extends Component {
public string $title = '';
};
?>
<div>…</div><?php
use Livewire\Component;
new class extends Component {
public string $title = '';
};
?>
<div>…</div>13. Die neuen Features übernehmen
1. Islands auf Ihrer langsamsten Seite
<div>
<div class="card">Umsatz: {{ $this->revenue }}</div>
<div class="card">Offene Jobs: {{ $this->queue }}</div>
<div class="card">Letzte Aktivität: …</div>
</div><div>
@island(name: 'revenue')
<div class="card">Umsatz: {{ $this->revenue }}</div>
@endisland
@island(name: 'queue', poll: '5s')
<div class="card">Offene Jobs: {{ $this->queue }}</div>
@endisland
@island(name: 'activity', lazy: true)
@placeholder
<div class="card skeleton animate-pulse h-48"></div>
@endplaceholder
<div class="card">Letzte Aktivität: …</div>
@endisland
</div>2. Round Trips durch Client-Direktiven ersetzen
<button wire:click="$toggle('showFilters')">Filter</button>
@if ($showFilters)
<div class="filters">…</div>
@endif<button wire:click="$toggle('showFilters')">Filter</button>
<div class="filters" wire:show="showFilters">…</div>3. SortableJS entfernen
<ul wire:sort="reorder">
@foreach ($tasks as $task)
<li wire:sort:item="{{ $task->id }}" wire:key="task-{{ $task->id }}">
<span wire:sort:handle>⠿</span> {{ $task->title }}
</li>
@endforeach
</ul>4. Kindkomponenten zusammenführen, die nur fürs Rendern existierten
Hat ein Kind keinen eigenen Zustand und wird nicht wiederverwendet, ist ein Island im Elternteil leichter — eine Komponente, eine Klasse, ein Snapshot.
5. #[Renderless] für unsichtbare Arbeit
use Livewire\Attributes\Renderless;
#[Renderless]
public function trackView(): void
{
$this->post->increment('views');
}6. Umstellung auf Single-File — zuletzt
Das ist Kosmetik. Machen Sie es beiläufig, wenn Sie die Datei ohnehin anfassen, nicht als eigenen Migrationssprint.
14. Upgrade-Checkliste
Vor dem Start
- Die Testsuite läuft auf Livewire 3 grün.
- Ein Branch und ein Datenbank-Backup, wenn die Umgebung geteilt wird.
- Jemand, der die App danach durchklickt — die lautlosen Fehler sieht nur ein Mensch.
Mechanische Änderungen
composer require livewire/livewire:^4.0php artisan optimize:clearlayout→component_layoutlazy_placeholder→component_placeholderRoute::get(…, Component::class)→Route::livewire(…)- Alle
<livewire:…>-Tags selbstschließend wire:model.blur/.change→.liveergänzen- Wrapper-
wire:model→.deepergänzen wire:scroll→wire:navigate:scrollwire:transition-Modifikatoren → ins CSS- Argumentreihenfolge von
$this->stream() $wire.$js('name', …)→$wire.$js.name = …Livewire.hook(…)→interceptRequest()/interceptMessage()- Volt-Klassen, -Routen, -Tests und -Paket entfernt
Infrastruktur
- nginx-/Apache-Location-Blöcke auf
/livewire-{hash}/erweitert - WAF-, CDN- und Firewall-Regeln aktualisiert
- Rate Limiter und Monitoring umgestellt
connect-srcin der CSP geprüft
Verifikation
- Testsuite grün
- Alle Formulare senden und validieren
- Datei-Uploads funktionieren, inklusive Fortschrittsanzeige
- Tabellen paginieren, sortieren und filtern
- Modals öffnen und schließen
- Drittanbieter-Widgets in
wire:ignoreinitialisieren weiterhin - Browser-Konsole sauber in den Hauptabläufen
- Update-Anfragen liefern in der Produktion 200, nicht 403 von der WAF
Offizielle Quellen
- Upgrade Guide: livewire.laravel.com/docs/upgrading
- Livewire-Dokumentation: livewire.laravel.com/docs