Laravel 12 Volt e Folio: costruire UI reactive senza JavaScript con Livewire
A luglio 2025 ho preso in mano un pannello di amministrazione per un'azienda del settore servizi professionali - 30 sezioni funzionali, dalla gestione anagrafica clienti alla reportistica fatturato, dalla pianificazione risorse alla dashboard operativa. Il pannello era stato costruito due anni prima con React 18 sul frontend e API Laravel sul backend: due repository separati, due pipeline di build, due deployment indipendenti. Il problema non era che il pannello funzionasse male - funzionava. Il problema era che il team di sviluppo era composto da tre programmatori PHP senior e nessun frontendista. Ogni modifica al pannello - aggiungere un campo a un form, modificare una tabella, cambiare un filtro - richiedeva di aprire il repository React, capire la struttura dei componenti, modificare lo state management con Zustand, aggiornare le chiamate API, testare, buildare con Vite e deployare. Tre ore per un campo in più in un form. Il titolare mi ha chiesto se esistesse un modo per rendere il pannello gestibile dal team PHP senza assumere un frontendista dedicato. La risposta si chiama Livewire Volt.
In due settimane ho riscritto l'intero pannello usando Livewire con la sintassi Volt su Laravel 12: 30 sezioni funzionali, stessa interattività del pannello React (tabelle con ordinamento e filtri live, form con validazione in tempo reale, dashboard con aggiornamenti automatici), zero righe di JavaScript custom, zero processo di build frontend, un unico repository, un unico deployment. Il team PHP ora modifica il pannello con la stessa facilità con cui modifica un controller Laravel. La produttività sulla manutenzione del pannello è passata da "tre ore per un campo" a "venti minuti per un campo".
Perché un team PHP non dovrebbe essere costretto a gestire un frontend React separato?
La risposta è che nella stragrande maggioranza delle applicazioni business interne - pannelli admin, CRM, gestionali, dashboard operative - la complessità di un frontend JavaScript separato non è giustificata dal valore che produce. React, Vue e Angular sono strumenti potenti per applicazioni con requisiti di interattività complessi: editor visuali, mappe interattive, canvas di disegno, chat in tempo reale con typing indicator. Per un CRUD con filtri, tabelle paginabili e form con validazione, sono un cannone per sparare a un passero - e quel cannone costa caro in termini di manutenzione, competenze richieste e tempo di sviluppo.
I dati lo confermano: nel mio lavoro con PMI italiane, il pannello di amministrazione è quasi sempre l'applicazione più modificata e meno curata. Viene costruito una volta con la tecnologia "alla moda" del momento, poi il team che l'ha costruito va avanti, e il team che lo mantiene (quasi sempre generalisti PHP, non specialisti frontend) lo tratta come una scatola nera che non osa toccare. Il risultato è un pannello che accumula debito tecnico più velocemente di qualsiasi altra parte del sistema. Livewire risolve questo problema alla radice: porta l'interattività nel dominio di Blade e PHP, dove il team che mantiene l'applicazione ha le competenze per lavorare efficacemente. Nel mio profilo professionale trovi il dettaglio dell'esperienza che porto in queste decisioni architetturali - la scelta tra stack JavaScript separato e stack full-PHP è una delle più impattanti sulla sostenibilità a lungo termine di un progetto.
Volt: logica PHP e template nello stesso file
Volt è la sintassi funzionale di Livewire che permette di scrivere componenti single-file: la logica PHP e il template Blade nello stesso file .blade.php. Con Livewire 4 (rilasciato a gennaio 2026 per Laravel 12), i single-file component sono diventati parte nativa del core - non serve più il pacchetto Volt separato. Il risultato è un modello di sviluppo che ricorda la produttività dei single-file component di Vue (.vue files), ma interamente in PHP.
Ecco un componente Volt completo per una tabella con ricerca live e paginazione - il tipo di interfaccia che in React richiede un componente, un hook useEffect, uno state per il filtro, una chiamata API e un componente di paginazione:
<?php
// resources/views/pages/clienti/index.blade.php
// Un solo file: route, logica e template
use App\Models\Cliente;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new class extends Component {
use WithPagination;
// Proprietà reattive: ogni modifica aggiorna il DOM automaticamente
public string $ricerca = '';
public string $ordinamento = 'ragione_sociale';
public string $direzione = 'asc';
// Quando la ricerca cambia, torna alla pagina 1
public function updatedRicerca(): void
{
$this->resetPage();
}
public function ordina(string $campo): void
{
if ($this->ordinamento === $campo) {
$this->direzione = $this->direzione === 'asc' ? 'desc' : 'asc';
} else {
$this->ordinamento = $campo;
$this->direzione = 'asc';
}
}
public function with(): array
{
return [
'clienti' => Cliente::query()
->when($this->ricerca, fn ($q) => $q->where('ragione_sociale', 'like', "%{$this->ricerca}%"))
->orderBy($this->ordinamento, $this->direzione)
->paginate(25),
];
}
}; ?>
<div>
<input type="text" wire:model.live.debounce.300ms="ricerca"
placeholder="Cerca per ragione sociale...">
<table>
<thead>
<tr>
<th wire:click="ordina('ragione_sociale')" style="cursor: pointer">
Ragione sociale
</th>
<th wire:click="ordina('partita_iva')" style="cursor: pointer">
Partita IVA
</th>
<th wire:click="ordina('created_at')" style="cursor: pointer">
Data inserimento
</th>
</tr>
</thead>
<tbody>
@foreach ($clienti as $cliente)
<tr>
<td>{{ $cliente->ragione_sociale }}</td>
<td>{{ $cliente->partita_iva }}</td>
<td>{{ $cliente->created_at->format('d/m/Y') }}</td>
</tr>
@endforeach
</tbody>
</table>
{{ $clienti->links() }}
</div>Questo singolo file sostituisce: un componente React con useState e useEffect per il filtro, un hook per il debounce, una chiamata API con gestione del loading state, un componente di paginazione, un API endpoint Laravel con parametri di ricerca e ordinamento, e la configurazione del routing nel frontend. La ricerca è live (con debounce di 300ms per non bombardare il server), l'ordinamento si aggiorna al click sulla colonna, la paginazione è gestita dal trait WithPagination di Livewire, e la validazione dei parametri di ricerca è server-side per definizione - non c'è nessuna possibilità che un utente manipoli i dati dal browser per iniettare query SQL, perché il browser non ha accesso diretto alla query.
Folio: routing basato su file senza web.php
Laravel Folio è il complemento naturale di Volt: un router file-based che crea automaticamente le route in base alla struttura delle directory nella cartella resources/views/pages. Se crei un file resources/views/pages/clienti/index.blade.php, Folio registra automaticamente la route /clienti. Se crei resources/views/pages/clienti/[id].blade.php, Folio registra /clienti/{id} con il parameter binding. Nessuna riga nel file routes/web.php.
La struttura del pannello amministrativo dopo la migrazione era:
resources/views/pages/
├── index.blade.php → /
├── clienti/
│ ├── index.blade.php → /clienti
│ ├── [Cliente].blade.php → /clienti/{cliente}
│ └── crea.blade.php → /clienti/crea
├── ordini/
│ ├── index.blade.php → /ordini
│ └── [Ordine].blade.php → /ordini/{ordine}
├── fatture/
│ ├── index.blade.php → /fatture
│ └── [Fattura].blade.php → /fatture/{fattura}
└── dashboard/
└── index.blade.php → /dashboardIl model binding è automatico: [Cliente].blade.php riceve un'istanza di App\Models\Cliente come variabile $cliente disponibile nel template e nel componente Volt. Il middleware si applica con la direttiva <?php \Laravel\Folio\Middleware::class(['auth', 'verified']); ?> direttamente nel file - nessun gruppo di middleware nel web.php, tutto dichiarato dove serve. Per un pannello con 30 sezioni, il file routes/web.php è passato da 180 righe a 12 righe (le route API e quelle custom che non seguono il pattern CRUD).
Il confronto con l'approccio Inertia.js che ho descritto nel mio articolo dedicato è utile per capire quando usare uno e quando l'altro: Inertia è la scelta giusta quando vuoi il pieno potere di un framework JavaScript (Vue, React, Svelte) con le sue librerie di componenti, le animazioni, lo state management avanzato - ed è particolarmente forte per applicazioni customer-facing con requisiti di UX elevati. Livewire Volt è la scelta giusta quando il team è PHP-centrico, l'applicazione è un backend/admin/gestionale, e la priorità è la velocità di sviluppo e la semplicità di manutenzione.
Il deploy: da webpack + npm a zero build step
L'ultimo vantaggio che ha convinto il titolare della migrazione è stato il deploy. Con il pannello React, il processo di deploy era: pull dal repository frontend, npm install (2 minuti), npm run build (45 secondi), copia della cartella dist/ nella directory pubblica del server, invalidazione cache CDN. Ogni deploy richiedeva Node.js installato sul server o un CI/CD pipeline con una stage di build dedicata. Con Livewire Volt, il deploy è: git pull, php artisan optimize, fine. Non c'è un processo di build perché non c'è JavaScript custom da compilare. Livewire include il suo runtime (circa 45 KB minificato) come asset statico che viene servito direttamente. I componenti Volt sono file Blade PHP che vengono interpretati dal server a ogni richiesta - esattamente come qualsiasi altra vista Laravel.
Per il team di tre sviluppatori PHP, questo ha significato eliminare completamente la dipendenza da Node.js dall'infrastruttura di produzione, rimuovere la stage di build dal CI/CD (risparmiando circa 2 minuti per deploy), e poter fare deploy incrementali con la sicurezza di un rollback istantaneo via git revert. La documentazione ufficiale di Livewire 4 descrive in dettaglio il quickstart per i single-file component nativi, inclusa la nuova naming convention con il lightning bolt (⚡) che rende i componenti Livewire immediatamente riconoscibili nel file system.
Prestazioni e limiti: cosa aspettarsi in produzione
La domanda che ricevo più spesso su Livewire è: "Ma non è lento? Ogni interazione fa una richiesta al server." La risposta è: dipende dal tipo di interazione e dal tuning del server. Ogni volta che l'utente digita nel campo di ricerca (con debounce), clicca una colonna per ordinare o naviga tra le pagine, Livewire invia una richiesta AJAX al server, il server esegue il rendering del componente aggiornato e restituisce l'HTML differenziale. Su un VPS Hetzner CPX21 (3 vCPU, 4 GB RAM) con PHP-FPM 8.2 e OPcache configurato correttamente, il tempo di risposta per un aggiornamento di componente Livewire è tra 40 e 120 millisecondi - percettivamente istantaneo per l'utente.
Il vero rischio prestazionale non è la latenza di rete - è il payload size. Se un componente Livewire contiene una tabella con 500 righe e 12 colonne, ogni aggiornamento dello stato richiede di ricalcolare e trasmettere l'intero HTML del componente. La soluzione è lo stesso principio che applico in qualsiasi applicazione server-rendered: paginazione server-side obbligatoria per le tabelle, lazy loading per i componenti pesanti, e polling solo dove serve davvero (la dashboard operativa che mostra ordini in tempo reale, non la pagina anagrafica clienti che cambia una volta al giorno). Nel pannello migrato, il componente più pesante - la dashboard con 8 widget e aggiornamento automatico ogni 15 secondi - ha un payload medio di 12 KB per aggiornamento. Per confronto, il bundle React del vecchio pannello pesava 1,8 MB al primo caricamento.
C'è anche un limite architetturale da dichiarare con onestà: Livewire non è adatto per interazioni che richiedono latenza sotto i 16 millisecondi (il frame budget per 60fps). Drag-and-drop complessi, animazioni physics-based, canvas drawing, editor WYSIWYG avanzati - per queste interazioni serve JavaScript nativo o un framework frontend dedicato. Ma nel contesto di un pannello amministrativo, queste esigenze si presentano nel 2-3% dei casi. Per il restante 97%, Livewire Volt offre un rapporto produttività/prestazioni che nessun framework JavaScript separato può eguagliare quando il team è PHP-centrico.
Il pannello riscritto è in produzione da otto mesi. Il team PHP ha aggiunto 6 nuove sezioni e modificato 14 delle 30 originali senza nessun intervento esterno. Il tempo medio per aggiungere un nuovo CRUD completo (lista con filtri, form di creazione/modifica, dettaglio) è sceso da 16 ore (React + API) a 3 ore (Livewire Volt + Folio). Il refactoring ha eliminato un repository, una pipeline di build, una dipendenza da Node.js e la necessità di competenze frontend specialistiche per la manutenzione ordinaria - il tipo di semplificazione architetturale che nel mio lavoro di refactoring del codice PHP legacy considero il risultato più importante: non il codice più moderno, ma il codice più sostenibile per il team che deve mantenerlo. Se hai un pannello admin costruito con un framework JavaScript separato e il tuo team PHP lo tratta come una scatola nera, contattami per valutare insieme se una migrazione a Livewire Volt ha senso per il tuo caso specifico: in una giornata di lavoro analizziamo la codebase attuale, stimiamo lo sforzo di migrazione e definiamo un piano incrementale che non interrompe il servizio.