Laravel Livewire vs Inertia con Vue: Comparativa de desarrollos REALES

- Andrés Cruz - EN In english

En este análisis comparamos dos herramientas de scaffolding fundamentales para el ecosistema Laravel: Livewire e Inertia.js. Ambas se instalan sobre Laravel para potenciar nuestras capacidades de desarrollo, pero bajo filosofías radicalmente distintas: Livewire mantiene toda la lógica en el servidor usando PHP, mientras que Inertia delega la interactividad al cliente con un framework JavaScript.

En el caso de Inertia, utilizaremos Vue.js como framework de frontend (aunque Inertia también admite React y Svelte), mientras que Livewire es mi opción favorita para entornos administrativos por su estrecha integración con el núcleo de Laravel.

A lo largo de este artículo compararemos implementaciones reales: un DataTable, una To-Do List, un Blog, un Carrito de Compras y un formulario paso a paso, para determinar cuál tecnología se adapta mejor a cada tipo de proyecto.

DataTable

Video thumbnail

Índice de contenido

Filosofía de uso: ¿Cuál elegir?

No existe una herramienta mágica; todo depende de las necesidades de tu proyecto:

  • Inertia.js (Vue/React/Svelte): Es ideal cuando buscas una interactividad del lado del cliente extremadamente rica. Para mi propia academia, elegí Vue porque el ecosistema de Node.js y sus extensiones para el navegador no tienen rival en cuanto a componentes y librerías disponibles.
  • Livewire: Es imbatible en velocidad de desarrollo para paneles de administración (dashboards) y formularios complejos, ya que te permite programar casi todo en PHP sin abandonar el ecosistema Laravel.

Implementación de DataTables en Livewire

Livewire destaca por ser conciso. Al estar ligado directamente al servidor, la comunicación entre la vista y el backend es transparente para el desarrollador. En mi implementación, utilizo un sistema de componentes organizado para mantener el código limpio y reutilizable.

Para que el DataTable sea reutilizable en distintas secciones del proyecto, extendemos de una clase abstracta personalizada llamada DataTableComponent:

abstract class DataTableComponent extends Component
{
    use WithPagination;
    public string $sortColumn = 'id';
    public string $sortDirection = 'desc';
    public function sort(string $column): void
    {
        $this->sortColumn = $column;
        $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
    }
  • Clases Abstractas: Utilizamos herencia para definir comportamientos comunes (como la ordenación) sin repetir código en cada listado. La clase DataTableComponent encapsula toda la lógica de paginación y sorting.
  • Query Scopes: En Laravel, implementamos scopes en los modelos para centralizar los filtros de búsqueda. Esto nos permite aprovechar la potencia de Eloquent de forma implícita y mantener los controladores limpios.
  • Filtros Elegantes: En lugar de usar múltiples condicionales if, empleamos el método when() de Eloquent. Esto hace que el código sea más legible y eficiente, activando filtros solo cuando el usuario los solicita.
ponent
{
    #[URL]
    public ?string $search = null;
    public array $columns = [
        'id' => 'Id',
        'title' => 'Title'
    ];
    protected function getAllFilters(): array
    {
        return [
            'search' => $this->search
        ];
    }
    protected function getModelClass(): string
    {
        return Category::class;
    }
    public $categoryToDelete;
    function with(): array{
        $categories = Category::
            filterDataTable($this->getAllFilters())
            ->paginate(10);
        return [
            'categories' => $categories  
        ];
    }

En la vista Blade, el ordenamiento se dispara con un simple wire:click al servidor:

<button wire:click="sort('{{ $key }}')" class="flex items-center gap-1">

Código fuente en:

https://github.com/libredesarrollo/curso-libro-livewire-4

Implementación en Inertia.js (Vue 3)

Con Inertia, la lógica del servidor es similar, pero el lado del cliente se vuelve más complejo. Al no existir un puente tan directo como en Livewire, debemos gestionar la comunicación entre componentes de Vue de forma manual mediante el sistema de eventos y props.

Para hacer que la ordenación sea reutilizable en Vue, utilizamos Composables. Estos nos permiten compartir lógica reactiva entre componentes, pero el flujo de eventos resultante es más elaborado:

  1. Un componente hijo (la cabecera de la tabla) emite un evento al hacer clic.
  2. El padre recibe el evento y actualiza los parámetros de ordenación.
  3. Inertia realiza una petición GET al servidor para refrescar los datos sin perder el estado de la página.

Es decir, desde resources/js/components/shared/DataTable/DataTableHeader.vue:

@click="handleSort(key)"

Emitimos al componente padre resources/js/components/shared/DataTable/DataTable.vue:

<DataTableHeader
   ***
  @sort="handleSort"

Y este a su vez emite al padre de nivel superior en resources/js/pages/dashboard/post/Index.vue:

<DataTable
  ***
   @sort="applyFilters">

En Livewire, al tratarse de componentes PHP ligados al servidor, la comunicación es directa. En Inertia con Vue, la cadena de eventos entre componentes añade una complejidad considerable, especialmente cuando el árbol de componentes crece.

Código fuente en:

https://github.com/libredesarrollo/curso-libro-laravel-inertia-3/

⚔️ Conclusiones para el DataTable

Aquí es donde Livewire (con Flux) gana por goleada frente a la configuración estándar de Inertia:

  • Livewire + Flux: Flux es una integración perfecta entre Blade, Tailwind y Laravel. Nos proporciona componentes listos para usar (tablas, botones, calendarios, migas de pan) con una sintaxis limpia y declarativa. No necesitamos preocuparnos por pasar props complejas; simplemente definimos arrays en PHP y todo funciona.
  • Inertia: Por defecto, los componentes que vienen en los starters de Inertia suelen ser demasiado atómicos o insuficientes para casos reales. A menudo terminas implementando DataTables de forma manual, lo cual agrega carga de mantenimiento y lógica innecesaria a tu solución.

Estéticamente, puedes lograr el mismo resultado visual con ambas tecnologías, pero Livewire es mucho más directo. Como dice el dicho: «el mejor programador no es el que resuelve más problemas, sino el que los evita». Toda la lógica de eventos y comunicación que requiere Vue/Inertia se siente como un ruido innecesario cuando lo que buscas es funcionalidad pura ligada al servidor.

Nota del autor: Si quieres profundizar en estas implementaciones, recuerda que este contenido forma parte de mis cursos y libros completos de Laravel. Puedes encontrar todo el material, el código fuente y las guías detalladas en mi Academia.

En definitiva, para este apartado un enorme punto para Livewire:

  1. Livewire: 1
  2. Inertia: 0

Eventos, Anidamiento y Comunicación entre Componentes

Video thumbnail

Tras comparar el funcionamiento de los DataTables —donde Livewire resultó ganador por su sencillez y menor cantidad de lógica—, en este apartado analizamos la creación de formularios paso a paso y cómo cada tecnología gestiona la comunicación entre componentes anidados.

El Concepto de Componente

Un componente es una unidad de trabajo modular y reutilizable. En este ejercicio, buscamos modularidad total. Por ejemplo, el formulario de creación de contactos es un componente independiente que puede consumirse desde distintos contextos:

  1. Desde una página principal de «paso a paso».
  2. Dentro de una publicación de blog.
  3. Como un modal en otra sección del sitio.

Tanto Livewire (con sus componentes «vitaminizados») como Inertia (basado en Vue 3, React o Svelte) utilizan este concepto como eje central de su arquitectura.

Implementación en el Backend (Laravel)

En la parte del backend no hay grandes diferencias, ya que ambas tecnologías se apoyan sobre la misma base de Laravel:

  • Livewire: Gestiona las validaciones directamente en la clase del componente o mediante Form Objects. Al hacer el submit, se aplican las reglas definidas con el atributo #[Validate] y se ejecuta el CRUD típico de Laravel.
  • Inertia: Aunque el controlador es prácticamente idéntico al de una aplicación Laravel estándar, solemos delegar las validaciones a un Form Request o una clase aparte para mantener la separación de responsabilidades.
new #[Layout('layouts.contact')] class extends Component {
    use WithFileUploads;

    public $step = 1;

    #[Validate('required|min:2|max:255')]
    public $subject;

    #[Validate('required|min:2|max:255')]
    public $message;

    #[Validate('required')]
    public $type = 'person';

    public $contactGeneral;

Gestión del Frontend y Reutilización

Aquí es donde realmente medimos la facilidad para adaptar y reutilizar componentes entre distintos contextos del proyecto.

El Acierto del Layout Automático

Ambas tecnologías resuelven de forma elegante la detección automática del layout:

  • Inertia: Si un componente se consume mediante una ruta, carga el layout definido; si se consume como un componente hijo dentro de otra página de Vue, el layout no se carga, evitando duplicidad de plantillas.
  • Livewire: Sucede exactamente lo mismo. Si se invoca como componente dentro de otra vista Blade —por ejemplo, con @livewire('show-contact')—, el layout principal no se renderiza.

Esto es una ventaja enorme frente al Laravel base sin Livewire, donde un @extends en Blade cargaría la plantilla maestra sin importar cómo se consuma la vista.

Reactividad y Comunicación: El Punto Crítico

La Complejidad en Livewire

En Livewire, la reactividad puede volverse confusa debido al acoplamiento entre PHP y JavaScript.

  • Eventos: Para comunicar un componente hijo con el padre, usamos el sistema de eventos con $dispatch. Sin embargo, al leer el código, no siempre queda claro qué componente está escuchando ese evento si no tienes el árbol completo en mente.
    •    #[On('stepEvent')]
          public function stepEvent($step)
          {
              $this->step = $step;
          }
          ***
          <flux:button wire:click="$dispatch('stepEvent',[1])">Back</flux:button>
  • Alpine.js e Interacción: Livewire utiliza Alpine.js para manejar la reactividad en el cliente. Aunque es potente, mezclar wire:model, wire:get y objetos de Alpine puede generar «ruido» visual y mental, especialmente para quien está empezando.
    • <div x-data="{ active:$wire.entangle('step') }" class="flex mx-auto flex-col sm:flex-row">
  • Limitación clave: Si un componente hijo no ha sido renderizado (por estar bajo un condicional @if), no puede recibir eventos del padre fácilmente, lo que limita la fluidez en flujos con múltiples pasos condicionales.

La Claridad en Inertia (Vue 3)

Inertia ofrece una experiencia más modular y predecible gracias al sistema reactivo nativo de Vue:

  • Separación de conceptos: El controlador devuelve datos y Vue se encarga del 100% de la lógica del cliente, sin mezclar paradigmas.
  • Props y Eventos: Al usar Vue, la comunicación mediante props y emits es estándar y predecible. Si ves un evento, sabes exactamente dónde está definido en el código JavaScript.
    • <ContactCompany
          :contactGeneralId="contactGeneral.id"
          @back-step-event="backStep"
          v-if="$page.props.step == 2"
          :contactCompany="contactGeneral.company"
      />

Reactividad Real: Vue es extremadamente inteligente en este aspecto. Incluso si un componente está bajo un v-if, en el momento de renderizarse, la comunicación y el estado se sincronizan de forma natural. Podemos comunicar el componente hijo al padre aunque el componente hijo no se haya cargado inicialmente, algo que Livewire no puede hacer con la misma fluidez.

  • En Livewire, si un componente hijo no se carga al inicializar la página, no puedes enviarle mensajes del padre de forma directa:
    • <livewire:contact.company :parent-id="$contactGeneral->id" />
      ***
      company.blade.php
       // No funciona porque sus hijos no están renderizados por el @if/Blade
          #[On('parentId')]
          public function setParentId(int $parentId): void
          {
              $this->parentId = $parentId;
      
              if ($this->parentId) {
                  $modelClass = $this->getModelClass();
                  $this->model = $modelClass::where('contact_general_id', $this->parentId)->first();
                  $this->setModelData($this->model);
              }
          }

Veredicto: ¿Cuál es Mejor para un Formulario «Paso a Paso»?

En este ejercicio específico, Inertia con Vue se lleva el punto por las siguientes razones:

  • Depuración más sencilla: Al no estar tan fuertemente acoplado el servidor con el cliente en cada pequeña interacción, es más fácil aislar y analizar dónde falla algo.
  • Manejo de Errores: En Inertia, el objeto form de Vue maneja los errores de forma local y modular. En Livewire, si no se gestiona bien, el objeto de errores global puede causar conflictos al anidar múltiples formularios en la misma vista.
  • Expresividad: La sintaxis de Vue para manejar estados complejos (como el paso actual de un formulario multi-etapa) resulta más natural y menos propensa a errores de «ida y vuelta» (roundtrip) innecesarios al servidor.
  • Resumen: Mientras que Livewire ganó en el DataTable por su velocidad de implementación, Inertia gana en componentes complejos y anidados por su robustez y reactividad real del cliente.

En definitiva, aunque yo soy Team Livewire, Inertia se merece su punto en este módulo:

  1. Livewire: 1
  2. Inertia: 1

To-Do List

Video thumbnail

Comparamos dos implementaciones de una aplicación de To-Do List: una con Inertia.js (Vue) y otra con Livewire apoyado en Alpine.js. La aplicación es sencilla pero funcional: incluye las operaciones CRUD (Crear, Leer, Actualizar, Borrar) centralizadas en una sola pantalla, lo que nos permite evaluar qué flujo de desarrollo resulta más eficiente.

Funcionalidades y Experiencia de Usuario

Ambas versiones cuentan con edición en línea. Al seleccionar una tarea, podemos editarla directamente y los cambios persisten en la base de datos sin recargar la página.

  • Gestión de Tareas: Marcado y desmarcado de ítems como completados, creación de nuevas tareas y eliminación individual.
  • Diseño y Estilos: El diseño varía ligeramente entre las dos versiones. En una utilicé componentes predefinidos de Flux, mientras que en la otra apliqué estilos con Tailwind CSS desde cero.
  • Interactividad: Ambas versiones incluyen sistema de Drag & Drop para el reordenado de tareas y búsqueda en tiempo real.

Tecnologías y Dependencias

El enfoque principal de esta comparativa es evaluar el desarrollo en el cliente: Vue.js frente a Livewire + Alpine.js.

  • Backend: En ambos casos es muy similar, basándose en controladores clásicos de Laravel con Eloquent.
  • Frontend: Utilizamos Sortable.js como dependencia externa para el reordenamiento mediante arrastrar y soltar.
  • Origen del proyecto: La versión de Livewire nació de una aplicación que primero desarrollamos 100% en Alpine.js (disponible en mis libros y cursos), para luego demostrar cómo adaptarla e integrarla con el servidor de forma transparente.

Análisis del Servidor (Lógica Backend)

Implementación con Inertia

En Inertia trabajamos con controladores tradicionales de Laravel. Tenemos métodos para obtener el listado, crear y actualizar tareas. Incluimos lógica de ordenación mediante un foreach que actualiza la posición de los IDs recibidos, y siempre filtramos por el usuario autenticado para garantizar la seguridad de los datos.

Implementación con Livewire

Aquí utilizamos un componente «todo-en-uno». Definimos las reglas de validación con #[Validate], el método mount() para cargar los datos iniciales, y las funciones de save(), delete() y setPositions(). El modelo no tiene mayor misterio; es una estructura sencilla que funciona igual para ambas tecnologías.

El Reto del Cliente: Vue.js vs. Livewire + Alpine

El verdadero reto está en el lado del cliente, donde cada tecnología muestra su filosofía de forma más clara.

  • Alpine.js con Livewire: Definimos un bloque x-data. En este proyecto, al ser una lógica compleja, la extraje a una función aparte. Utilizamos la directiva @script para cargar el JavaScript de Livewire correctamente y evitar errores de renderizado. Lo más destacable es la comunicación directa: usamos $wire.dispatch() para despachar acciones al servidor sin necesidad de configurar rutas manuales ni usar Axios o Fetch.
    • <div x-data="data()" x-init="order()" class="max-w-xl mx-auto py-8">
          <flux:card>
              ***
              <div class="mt-6">
                  <ul x-ref="items" class="space-y-2" wire:ignore>
                      <template x-for="t in filterTodo()" :key="t.id">
                          <li :id="t.id" class="flex items-center gap-3 p-3 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
                              <input 
                                  type="checkbox" 
                                  x-model="t.status" 
                                  @change="$wire.dispatch('update', { todo: t })"
                                  class="w-5 h-5 rounded border-zinc-300 text-purple-600 focus:ring-purple-500"
                              >
                              <div class="flex-1">
                                  <template x-if="completed(t)">
                                      <span class="text-green-600 text-sm font-medium">Completado</span>
                                  </template>
                                  <template x-if="!completed(t)">
                                      <span class="text-orange-600 text-sm font-medium">Pendiente</span>
                                  </template>
                                  <span x-text="t.name" @click="t.editMode=true" x-show="!t.editMode" class="block mt-1"></span>
                                  <flux:input 
                                      type="text" 
                                      @keyup.enter="t.editMode=false; $wire.dispatch('update', { todo: t })"
                                      x-model="t.name" 
                                      x-show="t.editMode" 
                                      class="mt-1"
                                  />
                              </div>
                              <flux:button variant="danger" size="sm" @click="remove(t)" icon="trash">
                              </flux:button>
                          </li>
                      </template>
                  </ul>
  • Vue.js con Inertia: Para muchos es más elegante por ser un framework completo y maduro. Importamos componentes, definimos props para el listado de tareas y manejamos el formulario con el helper form.post() de Inertia. Aunque es más estructurado y mantiene una separación clara entre cliente y servidor, requiere definir rutas en web.php y gestionar un archivo de rutas JS generado automáticamente con Ziggy.
    • <template>
          <WebLayout>
              <o-modal v-model:active="confirmDeleteActive">
                  <p class="p-4 text-black">
                      Are you sure you want to delete the record?
                  </p>
                  <div class="flex flex-row-reverse gap-2 bg-gray-100 p-3">
                      <o-button variant="danger" @click="remove">Delete</o-button>
                      <o-button @click="confirmDeleteActive = false">Cancel</o-button>
                  </div>
              </o-modal>
      
              <div class="mycard mx-auto mt-10 max-w-2xl">
                  <div class="mycard-body">
                      <form @submit.prevent="create" class="mb-2 flex gap-2">
                          <div class="flex-1">
                              <Input v-model="form.name" placeholder="What needs to be done?" />
                          </div>
                          <Button :disabled="form.processing">Send</Button>
                      </form>
      
                      <ul ref="todoListRef" class="mt-6">
                          <li v-for="element in dtodos" :key="element.id"
                              class="group mt-2 flex items-center rounded-lg border bg-white px-4 py-3 shadow-sm">
                              <span class="drag-handle mr-2 cursor-grab text-gray-400">::</span>
                              <div class="ml-3 flex-1">
                                  <span v-if="!element.editMode" @click="element.editMode = true"
                                      class="block w-full cursor-pointer">
                                      {{ element.name }}
                                  </span>
                                  <Input v-else v-model="element.name" @keyup.enter="update(element)"
                                      @blur="element.editMode = false" auto-focus />
                              </div>
                          </li>
                      </ul>
                  </div>
              </div>
          </WebLayout>
      </template>

Conclusión: ¿Cuál Elegir?

Si comparamos líneas de código, ambas implementaciones están muy igualadas (alrededor de 250 líneas en total).

  • Inertia (Vue): Es más estructurado y robusto, pero el coste es escribir más código y gestionar más archivos distribuidos entre controladores, rutas y componentes Vue.
  • Livewire (Alpine): Es más simple y elegante en su integración. Todo se siente más cohesionado (Livewire, Alpine y plugins de terceros), lo cual es precisamente la magia de este ecosistema: la velocidad de desarrollo sin cambiar de paradigma.

Para esta comparativa, declaro un empate técnico:

  1. Livewire: 2
  2. Inertia: 2

Blog

Video thumbnail

En esta nueva comparativa, analizamos el desarrollo de un Blog (listado y detalle con filtros). Recordemos el marcador hasta este punto:

  • DataTables: Ganó Livewire por ser más reactivo y sencillo de mantener.
  • Formulario Paso a Paso: El punto fue para Inertia, ya que al ser componentes del lado del cliente, Vue supera a Alpine.js en potencia reactiva.
  • To-Do List: Empate técnico.

El Reto del SEO en Aplicaciones SPA

Un punto crítico al desarrollar un blog público es el SEO. Tecnologías de cliente como Vue, React o Svelte tienen problemas en este aspecto porque el contenido se carga de forma asíncrona mediante JavaScript, y Google no siempre indexa correctamente lo que no está presente en el HTML inicial del servidor.

Sin embargo, Inertia.js resuelve esto con SSR (Server Side Rendering). Al usar la función Inertia::render() en lugar del view() tradicional de Laravel, el servidor procesa el componente y envía los datos (título, metadatos y contenido) ya renderizados en el HTML. Esto permite que Google lea el contenido en cuanto carga la página, sin esperar a que se ejecute el JavaScript del cliente (como ocurriría en el ciclo de vida onMounted).

Por su parte, Livewire no tiene este problema de origen, ya que es una tecnología que nace en el servidor y renderiza HTML directamente desde la primera respuesta HTTP.

Rendimiento y Carga de Recursos

Un blog debe ser ligero y cargar rápido. En mi experiencia personal (como en mi propio blog Desarrollo Libre), intento que la página no se bloquee por recursos externos que retrasen el First Contentful Paint.

  • Inertia: Carga el core de Vue e Inertia, lo cual añade algo de peso en JS, aunque es manejable con code splitting y lazy loading de rutas.
  • Livewire: Carga su propio script de JavaScript, que incluye el sistema de actualizaciones por AJAX.

En mi caso, incluso usando Livewire, prefiero excluir sus scripts en las secciones que no requieren reactividad y usar Alpine.js solo para detalles mínimos (como resaltar el botón activo del menú). Cargo todo el JS no esencial (publicidad, scripts secundarios) de forma asíncrona para que la experiencia del usuario sea instantánea desde el primer momento.

Desarrollo y Complejidad del Código

Al comparar el código de ambas implementaciones para el módulo de Blog:

  1. Lógica de Servidor: Es prácticamente idéntica. Ambos aprovechan la potencia de Laravel —como los Scopes de Eloquent para filtros—, por lo que aquí no hay un ganador claro.
  2. Longitud del Código (Vista/Componente):
    1. Livewire: Gana en simplicidad. El archivo show.blade.php tiene unas 87 líneas que contienen toda la lógica y el diseño de la vista.
    2. Inertia: El componente Vue sube a unas 102 líneas, a lo que hay que sumar el código del controlador dedicado.

A pesar de que Inertia es algo más extenso, nos da acceso a todo el ecosistema de plugins de Vue y a sus DevTools, lo cual es una ventaja implícita para proyectos que requieren mayor interactividad.

Resultado: Empate

Tanto Inertia como Livewire resuelven el módulo de blog con solvencia. Inertia compite en el terreno del SEO gracias al SSR, y Livewire sigue siendo el rey de la velocidad de desarrollo. El marcador global queda 3 a 3.

  1. Livewire: 3
  2. Inertia: 3

Carrito de Compras

Video thumbnail

Vamos con otro de los módulos clave: el carrito de compras. El funcionamiento se basa en la filosofía de mi plataforma, donde manejo distintos tipos de publicaciones: posts normales, cursos, libros y anuncios.

Para los ítems de tipo «publicidad» (que en este contexto actúan como productos), empleamos el carrito de compras. Lo más interesante aquí es la comunicación entre componentes, similar a lo que analizamos en el módulo de «paso a paso».

Estructura y Funcionamiento del CRUD

La estructura se organiza en torno al componente principal Cart, que se utiliza tanto para la página dedicada del carrito como para el modo embebido en el detalle de producto. Dentro de este, empleamos otro componente llamado CartItem.

Lógica del ítem:

El CartItem gestiona el CRUD del proyecto. Un ítem puede modificarse para aumentar su cantidad o eliminarse (estableciendo la cantidad en cero).

  • Gestión en el Frontend: Utilizamos un array donde el post_id sirve como referencia única, lo que evita tener que iterar todo el carrito para saber si un producto ya existe.
  • Gestión en el Backend: La lógica se mantiene idéntica entre Inertia y Livewire; solo cambia si la defines en un controlador tradicional o en una clase de componente de Livewire.
<?php

use Livewire\Component;
use Livewire\Attributes\Layout;
use Flux\Flux;
use App\Models\Post;
use App\Models\ShoppingCart;

new #[Layout('layouts.web')] class extends Component
{
    protected $listeners = ['itemDelete' => 'getTotal', 'itemAdd' => 'getTotal', 'itemChange' => 'getTotal'];

    public $type = 'list';
    public $post;
    public $cart;
    public $total;

    function mount(?Post $post, $type = 'list')
    {
        $this->type = $type;
        $this->post = $post;
        $this->cart = session('cart', []);
        $this->getTotal();
    }

    function addItem(Post $post)
    {
        $cart = session('cart', []);
        $cart[$post->id] = [$post->id, 'count' => 1];
        session(['cart' => $cart]);
        $this->dispatch('itemAdd');
    }

    public function getTotal()
    {
        if (auth()->check()) {
            $this->total = ShoppingCart::where('user_id', auth()->id())->sum('count');
        }
    }
};

Reactividad: Livewire vs. Inertia

Aquí es donde encontramos el «talón de Aquiles» de Livewire para este tipo de módulos.

El Problema de Livewire:

En Livewire, la comunicación entre componentes no es automática. Si modificas un CartItem, debes notificar manualmente al componente padre para que recargue el total o actualice la interfaz.

A diferencia de los frameworks de JavaScript, Livewire no tiene reactividad real del cliente, sino una «simulada» mediante peticiones al servidor. Esto hace que la sincronización entre componentes sea más manual y, en algunos casos, propensa a inconsistencias visuales si los eventos no se gestionan correctamente.

La Ventaja de Inertia (Vue/React):

En Inertia con Vue, la reactividad es natural y fluida. No necesitas emitir eventos manualmente para actualizar el total del carrito; simplemente realizas la operación con router.post() y, al actualizarse el estado compartido, todos los componentes que dependen de esos datos se refrescan automáticamente.

Los Eventos en Livewire

Lo que menos me gusta del sistema de eventos de Livewire es su método dispatch(). Aunque es potente, se vuelve extremadamente abstracto en proyectos con muchos componentes.

  • Falta de referencia explícita: Puedes emitir un evento con $this->dispatch('itemAdd'), pero no siempre es evidente qué componente lo está escuchando. Si retomas un proyecto meses después, pierdes rápidamente la pista del flujo.
  • Bajo acoplamiento: ventaja y desventaja: Es bueno porque permite comunicar cualquier componente entre sí, pero malo porque puede generar una estructura desorganizada donde los eventos se disparan sin una jerarquía clara de dependencias.

A nivel de líneas de código, ambos están muy parejos (alrededor de 160 líneas para los componentes más complejos).

Lo que me gusta de la estructura de Livewire: En términos de organización de archivos, Livewire te guía más hacia una estructura clara por defecto —las páginas en su carpeta Pages, separadas de los componentes reutilizables—, similar a cómo Laravel organiza modelos y controladores por convención.

$this->dispatch('itemAdd');

Esto NO Es un Componente, ES una Vista: La Diferencia de Modularidad

Esto NO Es un Componente, ES una Vista, NO Es Modular: Laravel Inertia vs Livewire
Video thumbnail

Esto que tenemos en Inertia no es un componente en el sentido estricto dentro del contexto de una aplicación Laravel. O al menos, no lo es de la misma manera que lo entendemos en Livewire.

¿A qué demonios me refiero?, te pudieras preguntar.

Te doy un poco de contexto. Esto es, como quien dice, una interpretación propia, nacida de haber desarrollado aplicaciones reales usando Laravel Inertia. Te explico por qué considero que lo que vemos aquí es, en esencia, simplemente una vista:

        <contact-layout>
           <general-form :errors="errors" :contactGeneral="contactGeneral"/>
           <company-form :contactCompany="contactGeneral.company"/>
        </contact-layout>

¿Pero… Cómo que No es un Componente?

Lo primero que podrías objetarme es:

«¡Mira, eso es claramente un componente Vue! ¿No lo estás viendo?»

Y sí, técnicamente tienes razón. El código anterior es un .vue, tiene su <template> y su bloque <script>. En el ecosistema de Vue, es un componente. Correcto.

Pero para entender mi punto de vista, hay que compararlo con cómo Livewire gestiona el mismo concepto. Lo que ocurre con Inertia es que usamos ese archivo .vue directamente como una vista —el destino final de una ruta—. Eso no lo convierte en un componente modular en el sentido de que puedas reutilizarlo como una unidad autónoma que carga sus propios datos desde el servidor.

Desde las Bases: Componentes en Laravel

En Laravel clásico, los componentes encapsulan elementos de UI reutilizables. Teníamos:

  1. Componentes anónimos (resources/views/components/), más organizados que una vista suelta.
  2. Componentes con clase, que requieren inicialización lógica y están ligados a una clase PHP.

Con esos conceptos claros: en Inertia, estamos obligados a pasarle toda la data manualmente desde el controlador para que el componente funcione. Al ser tecnologías independientes, no hay una integración profunda entre el servidor y el componente Vue.

Comparación con Livewire

En el equivalente de Livewire:

@livewire('contact.company', ['parentId' => $pk])

Solamente le pasamos un identificador. Con ese ID, Livewire resuelve y ejecuta internamente la clase relacionada:

    function mount($parentId)
    {
        $this->parentId($parentId);
    }

    function parentId($parentId)
    {
        $this->parentId = $parentId;
        $c = ContactCompany::where('contact_general_id', $this->parentId)->first();
        if ($c != null) {
            $this->name = $c->name;
            $this->identification = $c->identification;
            $this->extra = $c->extra;
            $this->choices = $c->choices;
            $this->email = $c->email;
        }
    }

    public function render()
    {
        return view('livewire.contact.company');
    }

Livewire ejecuta automáticamente el método mount(), inicializa el componente con sus propios datos y tú no te preocupas por pasarle manualmente toda la información desde el controlador.

Por eso considero que el componente de Inertia es, más bien, una vista renderizada con datos: no tiene un ciclo de vida propio controlado desde el servidor ni se auto-inicializa con su lógica de negocio.

Mi Fantasía Más Oscura… Livewire + Vue Sería Bellísimo

Video thumbnail

Fantaseando con Combinaciones

No quiero dar una opinión tajante, porque realmente no hay nada concreto que implementar hoy, pero sí me gusta reflexionar sobre combinaciones interesantes entre las tecnologías que uso a diario con Laravel.

Después de todos los análisis sobre Inertia, me encantaría imaginar una integración profunda entre Livewire y Vue: fusionar la reactividad y el ecosistema de Vue con las capacidades de auto-inicialización y comunicación servidor-cliente de Livewire.

Inertia: solo un cambio de Blade por Vue.

La idea básica de Inertia es muy sencilla: en lugar de devolver un Blade, Laravel devuelve un componente Vue. Eso es prácticamente todo.

  • Es fácil de iniciar con una curva de aprendizaje baja si ya conoces Vue.
  • Permite usar Vue sin separar el frontend y backend en proyectos distintos (sin necesidad de una REST API completa).

Livewire: Componentes de Laravel con «Vitaminas»

Livewire va un paso más allá:

  • No solo devuelve vistas, sino que las «vitamina» con reactividad bidireccional servidor-cliente.
  • Integra Alpine.js para manejar interacciones en el frontend sin peticiones al servidor.
  • Esto lo hace algo más complejo de dominar que Inertia en ciertos escenarios, pero considerablemente más potente para módulos administrativos.

Recomendaciones según tu perfil:

  • Si no conoces Vue: Livewire es más fácil de iniciar, ya que todo se escribe en PHP/Blade.
  • Si ya conoces Vue: Inertia es más natural y directo, aprovechando todo lo que ya sabes.

Personalmente, Vue es el único framework de cliente que uso. No trabajo con React ni Angular, y hasta ahora me ha sido más que suficiente.

Mi Fantasía: Livewire + Vue o Inertia Mejorado

  • Alpine.js es muy limitado para lógica compleja; lo veo casi como una herramienta para pequeñas interacciones puntuales donde no se justifica la carga de un framework completo.
  • La elección de Alpine para Livewire tiene sentido: sirve para animaciones puntuales e interacciones sencillas sin sobrecargar el bundle.

Pero fantaseando, sería interesante explorar:

  1. Usar Livewire con Vue como motor de plantillas, combinando la reactividad del cliente con la auto-inicialización del servidor.
  2. Que Inertia heredara características de Livewire, como la reutilización y comunicación de propiedades (wire:model vs v-model) directamente ligadas al servidor.

Cuándo NO es Necesario Usar Vue

  • Para formularios tradicionales o de mediana complejidad: Livewire + Alpine es más que suficiente.
  • Para validaciones simples o comunicación directa con el servidor sin mucha lógica de cliente.

Cuándo Sí es Útil Vue

  • Formularios complejos con animaciones dinámicas y lógica de estado condicional según selecciones del usuario.
  • Interfaces con muchas interacciones dinámicas, por ejemplo:
    • Feeds de redes sociales con actualizaciones en tiempo real.
    • Compartir contenido con animaciones y transiciones elaboradas.
    • Plugins visuales con burbujas, transiciones y efectos de partículas.

En estos casos, Alpine.js se queda corto, y Vue es mucho más potente y flexible. La documentación de Vue, su comunidad activa y la disponibilidad de plugins facilitan la implementación de funcionalidades avanzadas que en Alpine serían difíciles de replicar.

¿Para Qué Sirve Realmente Laravel Inertia?

Video thumbnail

Esta es una pregunta que me llevo haciendo desde hace meses y que quiero compartir con honestidad, incluyendo contexto sobre mis proyectos y experiencias reales.

En los proyectos con los que trabajo normalmente hay dos partes bien diferenciadas:

  • Parte administrativa: panel de control, dashboard, CRUD, gestión interna.
  • Parte del usuario final: blogs, ventas de productos, cursos, libros, reservas, etc.

Esto aplica a cualquier temática: vender carros, entradas, gorras, alquilar casas u hoteles… siempre hay una parte administrativa y otra visible para el usuario final, con necesidades completamente distintas.

SPA y SEO: Una Relación Complicada

Un problema importante con las SPA (Single Page Applications) es que no siempre se posicionan bien en motores de búsqueda. Google carga el contenido dinámico generado por JavaScript a trozos y muchas veces no «lee» correctamente la información, dificultando el SEO orgánico. Para páginas que queremos posicionar (hoteles, productos, blogs), el renderizado en servidor sigue siendo más recomendable.

Inertia: Vue en Lugar de Blade

La principal característica de Inertia es que devuelve un componente Vue en lugar de un Blade:

public function create()
{
    $categories = Category::get();
    return inertia("dashboard/post/Save", compact('categories'));
}

Eso es prácticamente el 90% de lo que ofrece Inertia a nivel conceptual. A diferencia de Livewire, no proporciona interacción directa con el backend mediante métodos del componente. En Livewire puedes tener un botón que ejecute un método PHP directamente con wire:click:

<x-button class="flex-shrink-0" wire:click="tagSave">
    {{ __('Set') }}
</x-button>

Y ese método vive en la clase del componente, sin necesidad de definir una ruta adicional ni usar Axios:

public function tagSave()
{
    if ($this->tag_selected != null) {
        $t = Tag::find($this->tag_selected);
        $this->tagsSelected[$t->id] = $t->title;

        if ($this->post)
            $this->post->tags()->sync(array_keys($this->tagsSelected));
    }
}

Inertia para Dashboards: No es la Mejor Opción

Para módulos administrativos, no recomendaría Inertia como primera opción.

  • Vue/Inertia está más pensado para el usuario final, donde las animaciones, la interactividad y la experiencia visual son el foco principal.
  • Un panel administrativo usualmente es más funcional y sencillo: menos estilo, menos animaciones, más eficiencia operativa.

En cambio, Livewire es más eficiente para dashboards:

  • Todo sigue siendo Blade/Laravel, sin saltar de paradigma.
  • Los métodos del componente se llaman directamente con wire:click sin necesidad de definir rutas adicionales ni Axios.
  • Mejor escalabilidad y modularización para operaciones CRUD y acciones complejas en el panel de administración.

REST API + Laravel: La Opción Más Escalable Hoy en Día

Prácticamente todas las aplicaciones web tienen su equivalente móvil: Udemy, Duolingo, Gmail… Si hubiera construido mi academia solo con Inertia, habría encontrado un problema importante:

  • Para crear la app móvil en Flutter, necesitaría duplicar cada controlador de Inertia para generar la REST API.
  • Cada cambio en la lógica web tendría que replicarse también en la API para la app móvil.
  • Esto genera redundancia y duplicación de lógica insostenible a medida que crece el proyecto.

Por eso, la arquitectura más recomendable para proyectos escalables es:

  1. Laravel + REST API: backend robusto, versionado y reutilizable por cualquier cliente.
  2. Vue/React/Flutter: frontend web o móvil que consume la API.
  3. Livewire: exclusivamente para dashboards donde se necesite interacción rápida sin duplicar lógica de API.
  • Inertia es útil si quieres reemplazar Blade por Vue en un proyecto web monolítico de rápido desarrollo.
  • No es ideal para dashboards administrativos ni para proyectos que planeen tener aplicaciones móviles.
  • Para proyectos escalables y multiplataforma: Laravel + REST API + frontend separado (Vue, React, Flutter) es la combinación más sólida.
  • Livewire sigue siendo excelente para dashboards y operaciones CRUD donde la integración directa con Blade/Laravel es una ventaja.

Lo Único que le Falta a Laravel Livewire para ser Perfecto: Un Gestor de Estado

Video thumbnail

Inertia y su Enfoque

Lo considero una tecnología útil principalmente si lo que quieres es trabajar con Vue directamente en Laravel, lo cual ya es bastante valioso. Pero, más allá de eso, no tiene un caso de uso que lo haga indispensable, salvo que quieras usar Vue sin montar una arquitectura de API separada.

Livewire: ¿Más Todoterreno?

Por otro lado, Livewire me parece mucho más «todoterreno» en comparación con Inertia. No es que sea la herramienta definitiva para todo, pero destaca por la flexibilidad que ofrece dentro del ecosistema Laravel-Blade.

Sin embargo, hay un aspecto que para mí marcaría una diferencia clave y que haría que Livewire fuera casi perfecto.

Alpine vs Vue: Lo que Me Gustaría Ver

Alpine.js a veces me resulta engorroso para lógica compleja. Sirve muy bien para cosas sencillas, pero cuando intentas escalar la interactividad del cliente, puede volverse un obstáculo. Quizás es en parte porque he trabajado mucho más con Vue, pero el contraste es evidente.

Todo esto surgió a raíz de un comentario que recibí recientemente. La persona decía que no le parecía complejo trabajar con Livewire. Y tiene razón —todo es relativo—. Yo lo percibo como complejo en ciertos escenarios porque he trabajado con muchas tecnologías y sé cómo resolver la misma lógica de formas más directas. Y el punto que siempre señalo es: la comunicación entre componentes anidados en Livewire puede convertirse en una pesadilla.

Comunicación entre Componentes: El Verdadero Dilema

Cuando tienes varios componentes anidados —no solo padre e hijo, sino nietos o bisnietos—, la gestión se vuelve muy engorrosa. Tienes que pasar mensajes mediante eventos con $dispatch(), registrar los listeners con el atributo #[On] en el padre, y desde ahí lanzar nuevos eventos hacia arriba. Todo eso puede ser muy abstracto y difícil de mantener para otro desarrollador que llegue al proyecto.

Me puse a pensar: ¿cómo resuelven este problema otras tecnologías más maduras?

Comparación con Otras Tecnologías: Vue y Flutter

Yo trabajo principalmente con Vue y Flutter, tecnologías fuertemente modulares y basadas en componentes (o widgets, en el caso de Flutter).

En ambas, la comunicación padre-hijo-nieto funciona bien a nivel básico, pero cuando la jerarquía crece demasiado, la solución recomendada es usar un gestor de estado.

En Flutter, hay varias opciones populares:

  1. Provider
  2. Bloc
  3. Redux
  4. Riverpod

Yo he trabajado principalmente con Provider y algo de Redux. Bloc nunca me convenció del todo, pero es una opción válida.

En Vue, los gestores de estado más conocidos son:

  1. Vuex: la opción clásica, más verbose.
  2. Pinia: más moderno, intuitivo y la opción oficial recomendada para Vue 3.

Personalmente, prefiero Pinia por su simplicidad y su integración natural con el Composition API de Vue 3.

¿Qué es un Gestor de Estado?

Un gestor de estado es una capa global que configuras sobre tu aplicación para compartir y modificar datos entre componentes de cualquier nivel de la jerarquía, sin necesidad de propagarlos manualmente por toda la cadena.

Por ejemplo, imagina que tienes el nombre del usuario en el layout principal y quieres modificarlo desde un componente cuatro niveles más abajo. Con un gestor de estado como Pinia:

  • Colocas el nombre en el store.
  • Cualquier componente puede acceder a ese dato mediante un getter.
  • Y cualquier componente puede modificarlo mediante una acción.

El cambio se refleja automáticamente en todo el árbol de componentes, sin tener que propagar eventos de bisnieto a nieto, de nieto a hijo y de hijo a padre, como ocurre actualmente en Livewire.

¿Por Qué Livewire Necesita Esto?

Livewire actualmente no tiene un gestor de estado nativo. Y eso complica mucho las cosas cuando el proyecto crece en complejidad y el árbol de componentes se profundiza.

Imagínate tener que pasar datos desde un componente bisnieto hasta el layout principal, pasando por toda la jerarquía intermedia con eventos… Es simplemente poco sostenible a largo plazo.

Es por eso que en Vue, React, Angular, Flutter y otros frameworks maduros surgieron los gestores de estado: para evitar exactamente este lío. Y por eso creo que Livewire también lo necesita urgentemente para dar el siguiente salto de madurez.

¿Cómo Podría Implementarse?

Idealmente, Laravel Livewire podría incorporar algo como:

  • Un componente especial que funcione como store global, similar a Pinia pero en PHP.
  • O una capa adicional que permita centralizar el estado y hacerlo accesible desde cualquier punto de la jerarquía.

No necesariamente debe ser un componente visual, sino algo que se configure una vez y permita compartir información de forma limpia entre cualquier componente Livewire, sin importar qué tan anidado esté.

Conclusión: ¿Quién Gana en la Comparativa Livewire vs Inertia?

Al inicio de este análisis, pensé que Livewire ganaría con claridad. Sin embargo, tras evaluar cada módulo en detalle, el resultado es un sorprendente empate técnico.

  • Inertia (Vue/React): Gana en la parte del cliente. Es imbatible para interfaces complejas con mucha comunicación entre componentes, reactividad fluida y acceso al ecosistema completo de npm.
  • Livewire: Es mi favorito para el 90% de los proyectos reales. Excelente para CRUDs, dashboards y aplicaciones donde no hay un anidamiento de componentes muy profundo. Es la herramienta más «generalista» y productiva para un desarrollador Laravel que quiere ir rápido sin salir del ecosistema PHP.

Al final, la puntuación queda 4 a 4. No es que una tecnología sea objetivamente mejor que la otra, sino que cada una brilla en su contexto. Para la parte de «presentación» al usuario final, prefiero la reactividad nativa de Vue con Inertia; para la gestión interna y la velocidad de desarrollo en el backend, me quedo con Livewire sin dudarlo.

  1. Livewire: 4
  2. Inertia: 4

Veremos una comparación entre Livewire e Inertia, desarrollos como Datatable, Blog, formularios pasos por pasos y veamos que conviene más y porqué.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.