Inertia.js con Laravel e Vue 3: full-stack senza API REST e senza SPA tradizionale

Inertia.js con Laravel e Vue 3: full-stack senza API REST e senza SPA tradizionale

Nell'estate del 2025 ho preso in carico la modernizzazione di un gestionale interno per un'azienda del settore logistico con circa 40 dipendenti. Il gestionale girava su Laravel 10 con frontend interamente basato su jQuery: 47 file JavaScript da 200-800 righe ciascuno, interfacce costruite con concatenazione di stringhe HTML dentro $(document).ready(), chiamate AJAX sparse e senza gestione degli errori centralizzata, e un'esperienza utente che ricordava il 2014 - submit del form, reload completo della pagina, flash message che spariva dopo il refresh. Il backend era solido: controller ben organizzati, Eloquent model con relazioni pulite, middleware di autenticazione e autorizzazione testati su tre anni di produzione. Il problema era tutto nel frontend, e il titolare mi aveva dato un budget e un vincolo: "Modifica l'interfaccia, ma non toccare il backend che funziona."

La prima tentazione di chiunque in questa situazione è costruire una SPA tradizionale con Vue 3 o React, esporre un'API REST dal backend Laravel, e consumarla dal frontend. È la scelta che va per la maggiore nei tutorial e nei corsi online, e sulla carta sembra l'approccio più "moderno". In pratica, per un gestionale di una PMI con 40 utenti interni e senza un team frontend dedicato, è la scelta peggiore possibile - perché trasforma un monolite funzionante in un sistema distribuito con due codebase, due layer di autenticazione, due punti di fallimento e il doppio della superficie di manutenzione. La soluzione che ho adottato è Inertia.js, una libreria che ha cambiato radicalmente il modo in cui costruisco applicazioni full-stack con Laravel dal 2023 in poi. Il risultato della migrazione è stato un gestionale con esperienza SPA completa - navigazione fluida, aggiornamenti reattivi, zero reload di pagina - senza un solo endpoint API REST e senza modificare una riga dei controller esistenti.

Il problema architetturale: SPA tradizionale, API REST e il debito tecnico che nessuno conta

Quando uno sviluppatore decide di costruire una SPA separata per consumare un backend Laravel, sta implicitamente accettando una serie di costi che raramente vengono contabilizzati nel preventivo iniziale. Il primo è il doppio layer di autenticazione: Laravel gestisce la sessione con cookie e Sanctum o Passport, mentre il frontend deve implementare un flusso di login separato, gestire il refresh dei token, intercettare le risposte 401 e redirigere al login. Il secondo è il doppio layer di validazione: ogni form deve validare sia sul frontend (per UX) sia sul backend (per sicurezza), e le regole devono restare sincronizzate manualmente. Il terzo è il doppio layer di autorizzazione: se un utente non può vedere un campo, il frontend deve saperlo e nasconderlo, ma l'API deve comunque non restituirlo - e i due devono essere coerenti. Il quarto, e spesso il più costoso nel lungo periodo, è la API contract maintenance: ogni modifica al backend che cambia la forma dei dati restituiti dall'API rischia di rompere il frontend, e senza una suite di test di integrazione robusta (che nella realtà delle PMI italiane quasi mai esiste) il problema viene scoperto in produzione dagli utenti.

Nel gestionale che ho migrato, c'erano 34 controller con 112 metodi. Costruire un'API REST per esporre quei dati avrebbe significato scrivere almeno 60-80 endpoint, con transformer, API Resource, pagination wrapper, error handling, rate limiting e documentazione. A 30-45 minuti per endpoint - tra codice, test e documentazione - parliamo di 30-60 ore di lavoro solo per l'API, prima ancora di scrivere una riga di frontend Vue. Con Inertia.js, quel lavoro è stato esattamente zero: i controller esistenti continuano a funzionare, semplicemente restituiscono una Inertia response invece di una Blade view. Nel mio profilo professionale trovi il dettaglio dell'esperienza che porto in questo tipo di decisioni architetturali - sono scelte che nel contesto delle PMI determinano se un progetto di modernizzazione finisce nei tempi e nel budget, o se si trasforma in una riscrittura infinita.

Cosa cambia davvero con Inertia.js rispetto a una SPA con API separata?

Inertia.js è un layer di collegamento tra il backend Laravel e il frontend Vue (o React, o Svelte) che elimina la necessità di un'API REST. Il tuo controller Laravel continua a funzionare esattamente come prima - routing server-side, middleware, validazione, autorizzazione - ma invece di restituire una vista Blade restituisce una pagina Inertia, che è un componente Vue con i dati già iniettati come props. Non c'è client-side routing, non c'è state management globale, non ci sono token da gestire.

Il flusso è questo: l'utente clicca un link, Inertia intercetta il click e fa una richiesta XHR al server. Il server elabora la richiesta normalmente (controller, middleware, Eloquent, tutto come sempre) e restituisce un JSON minimale con il nome del componente Vue da renderizzare e i dati da passargli come props. Inertia prende quel JSON, monta il componente Vue giusto e aggiorna il DOM senza un reload della pagina. Il risultato per l'utente è identico a una SPA: navigazione fluida, transizioni animate, form reattivi. Ma per lo sviluppatore il modello mentale è quello di una web app tradizionale: route, controller, return view.

// Controller Laravel - prima (Blade)
public function index(): View
{
    $ordini = Ordine::query()
        ->with(['cliente', 'righe.prodotto'])
        ->where('stato', 'attivo')
        ->orderByDesc('created_at')
        ->paginate(25);

    return view('ordini.index', compact('ordini'));
}

// Controller Laravel - dopo (Inertia)
// Stessa logica, unica differenza: il return
public function index(): Response
{
    $ordini = Ordine::query()
        ->with(['cliente', 'righe.prodotto'])
        ->where('stato', 'attivo')
        ->orderByDesc('created_at')
        ->paginate(25);

    return Inertia::render('Ordini/Index', [
        'ordini' => $ordini,
    ]);
}

La differenza è una riga. Il controller non sa e non gli interessa se il frontend è Vue, React o Svelte. Non serializza manualmente i dati in un JSON con API Resource. Non gestisce header CORS, non negozia token, non implementa pagination link custom. Restituisce i dati come farebbe con una Blade view, e Inertia si occupa del trasporto. Questo è il motivo per cui la migrazione del gestionale da jQuery a Vue 3 + Inertia ha richiesto zero modifiche ai controller: il backend era già corretto, mancava solo un frontend degno del 2025.

Autenticazione, form e middleware: dove Inertia elimina la complessità

L'autenticazione con Inertia è server-side al 100%. Non c'è un flusso OAuth separato, non ci sono token JWT da gestire nel localStorage - che è un antipattern di sicurezza documentato da OWASP nella cheat sheet sulla gestione dei token - non c'è un interceptor Axios che deve rinfrescare token scaduti. Il middleware auth di Laravel funziona esattamente come funzionava prima: se l'utente non è autenticato, viene rediretto al login. Se non ha i permessi, riceve un 403. Se la sessione scade, viene rediretto al login. Tutto gestito dal server, zero logica di autenticazione nel frontend.

I form sono l'altro punto dove Inertia elimina complessità rispetto a una SPA tradizionale. L'helper useForm di Inertia gestisce stato del form, submit, errori di validazione server-side e reset in poche righe:

<script setup>
// Componente Vue 3 - form di creazione ordine con Inertia
import { useForm } from '@inertiajs/vue3'

const form = useForm({
    cliente_id: null,
    note: '',
    righe: [],
})

// Submit del form: POST al controller Laravel, come sempre
const submit = () => {
    form.post('/ordini', {
        onSuccess: () => form.reset(),
    })
}
</script>

<template>
    <form @submit.prevent="submit">
        <select v-model="form.cliente_id">
            <!-- Le opzioni arrivano dal server come props -->
        </select>

        <!-- Errori di validazione Laravel, automatici -->
        <div v-if="form.errors.cliente_id" class="text-red-600">
            {{ form.errors.cliente_id }}
        </div>

        <textarea v-model="form.note" />

        <button type="submit" :disabled="form.processing">
            {{ form.processing ? 'Salvataggio...' : 'Salva ordine' }}
        </button>
    </form>
</template>

Gli errori di validazione che vedi nel template (form.errors.cliente_id) arrivano direttamente dal FormRequest di Laravel, senza nessuna mappatura manuale. Se il controller fa un $request->validate(['cliente_id' => 'required|exists:clienti,id']) e la validazione fallisce, Inertia cattura la risposta 422, popola automaticamente form.errors e il template si aggiorna in tempo reale. Niente catch di errori Axios, niente parsing manuale dei messaggi, niente gestione delle risposte HTTP nel frontend. La validazione vive dove deve vivere - nel backend - e il frontend la mostra e basta.

Inertia v2, rilasciato al Laracon US 2024, ha aggiunto tre funzionalità che rafforzano questo modello: polling per aggiornare i dati a intervalli regolari senza WebSocket (utile per dashboard e notifiche), prefetching per precaricare le pagine al hover del mouse (la navigazione diventa percettivamente istantanea), e deferred props per caricare prima i dati essenziali e poi quelli secondari (la pagina diventa interattiva in metà del tempo). Tutte e tre sono funzionalità server-driven che non richiedono logica aggiuntiva nel frontend.

La migrazione incrementale da jQuery: il pattern che funziona

La strategia che ho usato per il gestionale logistico non è stata una riscrittura totale - sarebbe stato troppo rischioso e troppo costoso per una PMI che doveva continuare a lavorare durante la migrazione. Ho adottato un approccio incrementale che chiamo page-by-page migration: converto una pagina Blade alla volta in un componente Vue + Inertia, deployando ogni pagina migrata separatamente, con rollback possibile in dieci secondi semplicemente riportando il metodo del controller a return view(...) invece di Inertia::render(...).

L'ordine di migrazione è stato guidato dal valore di business: prima le pagine che gli utenti usavano di più e che avevano i problemi di UX più gravi (la dashboard principale con 8 tabelle jQuery DataTables che caricavano 12 secondi di dati al primo load), poi le pagine dei form più complessi (creazione ordine con 4 livelli di relazioni nidificate), poi gradualmente tutto il resto. In otto settimane di lavoro distribuito, con deploy incrementali ogni 2-3 giorni, il gestionale è passato da 100% Blade+jQuery a 100% Vue 3+Inertia, senza un singolo giorno di downtime e senza che nessun utente abbia mai avuto l'applicazione non funzionante.

L'aspetto tecnico che ha reso possibile questa migrazione incrementale è la capacità di Inertia di coesistere con Blade nello stesso applicativo: durante la transizione, alcune route restituivano view() e altre Inertia::render(), e le due potevano coesistere nella stessa applicazione senza conflitti. Il Link component di Inertia gestisce solo i link interni alle pagine Inertia; se un link punta a una pagina Blade, Inertia lo tratta come un link tradizionale e fa un full page load. Questo significa che puoi migrare una pagina alla settimana senza mai rompere la navigazione dell'intero gestionale - lo stesso principio di migrazione incrementale che applico sistematicamente nel refactoring di codice PHP legacy verso architetture moderne, dove la regola d'oro è: mai smettere di funzionare, neanche per un giorno.

Il testing delle pagine Inertia è un altro punto dove il modello monolite vince: puoi testare l'intera interazione utente con i test HTTP di Laravel ($this->get('/ordini')->assertInertia(...)) senza bisogno di un browser headless, e il framework di test che suggerisco per le codebase PHP legacy si adatta perfettamente a questo approccio perché i test Inertia sono test Laravel standard che verificano le props passate al frontend.

Quando Inertia non è la scelta giusta

Sarebbe disonesto presentare Inertia come la soluzione universale per ogni progetto. Ci sono almeno tre scenari in cui una SPA con API REST separata resta la scelta corretta.

Il primo è quando il backend deve servire più di un client: se oltre al frontend web hai un'app mobile nativa, un'integrazione con sistemi terzi o un portale partner che consuma gli stessi dati, ti serve un'API REST (o GraphQL) indipendentemente dal frontend web. In quel caso, Inertia può ancora servire il frontend web mentre l'API serve i client esterni, ma il lavoro di costruire l'API devi farlo comunque.

Il secondo scenario è quando il tuo team ha competenze frontend forti e vuole pieno controllo su routing client-side, state management globale e caching avanzato lato client: Inertia semplifica molte cose, ma lo fa rimuovendo gradi di libertà che un team frontend esperto potrebbe voler mantenere.

Il terzo è quando hai requisiti di SEO su contenuti pubblici complessi: Inertia supporta server-side rendering (SSR) tramite un processo Node.js separato, ma la configurazione aggiunge complessità, e per un blog o un e-commerce con migliaia di pagine indicizzate da Google potresti preferire un framework SSR-first come Nuxt.js o Next.js.

Per i gestionali PMI, i portali B2B con utenti autenticati, i CRM interni, le dashboard operative e tutte le applicazioni dove il frontend è dietro un login e il SEO non è un fattore, Inertia è la scelta che nel mio lavoro ha dato i risultati migliori in termini di rapporto tra velocità di sviluppo, manutenibilità e qualità dell'esperienza utente. La documentazione ufficiale di Inertia.js v2 è il punto di partenza migliore per capire se questa architettura si adatta al tuo progetto, e include guide specifiche per Laravel con Vue 3, React e Svelte. Nel progetto del gestionale logistico, la dashboard che caricava in 12 secondi con jQuery e DataTables ora carica in 800 millisecondi con Vue 3 e paginazione server-side gestita da Inertia, gli utenti hanno smesso di lamentarsi dell'interfaccia, e il titolare ha commentato: "Sembra un'altra applicazione - ma fa le stesse cose." Questo è esattamente il risultato che cerco: modernizzare l'esperienza senza ricostruire il motore. Se hai un gestionale PHP o un portale B2B che funziona ma ha un frontend datato, e vuoi capire se Inertia è la strada giusta per modernizzarlo senza ricostruire il backend, contattami per una valutazione: in una sessione di due ore analizziamo la tua codebase, valutiamo la fattibilità della migrazione e definiamo un piano incrementale con tempi e costi realistici.

Ultima modifica: