In this analysis, we are going to compare two fundamental scaffolding tools for the Laravel ecosystem: Livewire and Inertia.js. Both are installed on top of Laravel to enhance our development capabilities, but under different philosophies.
In the case of Inertia, we will use Vue.js (although it has equivalents in React or Svelte), while Livewire is my favorite choice for administrative environments due to its tight integration with the Laravel core.
We will provide a comparison between various implementations such as Datatable, shopping cart, blog, and step-by-step forms to find out which technology suits us better, whether it is Livewire or Inertia.
Datatable
Content Index
- Datatable
- Philosophy of use: Which one to choose?
- Implementing DataTables in Livewire
- Implementation in Inertia.js (Vue 3)
- ⚔️ Conclusions for the Datatable
- Events, Nesting, and Communication between components
- The Concept of a Component
- Backend Implementation (Laravel)
- Frontend Management and Reusability
- The Success of the Automatic Layout
- Reactivity and Communication: The Critical Point
- Complexity in Livewire
- Clarity in Inertia (Vue 3)
- Verdict: Which is better for a "Step-by-Step"?
- To Do List
- Features and User Experience
- Technologies and Dependencies
- Server Analysis (Backend Logic)
- Implementation with Inertia
- Implementation with Livewire
- The Client Challenge: JS vs. Livewire + Alpine
- Conclusion: Which one to choose?
- Blog
- The SEO Challenge in SPA Applications
- Performance and Resource Loading
- Development and Code Complexity
- Conclusion: Who takes the point?
- Result: Tie.
- Shopping Cart
- CRUD Structure and Operation
- Item Logic:
- Reactivity: Livewire vs. Inertia
- The Livewire problem:
- The Inertia advantage (Vue/React):
- Events in Livewire
- This Is NOT a Component, IT'S a View, IT'S NOT Modular: Laravel Inertia vs Livewire
- But… how is it not a component?
- From the ground up: components in Laravel
- Comparison with Livewire
- What the HELL#%!& is Laravel Inertia for?
- SPA and SEO: a complicated relationship
- Inertia: Vue instead of Blade
- Inertia for dashboards: not the best option
- Rest API + Laravel: the ideal option nowadays
- The ONLY thing Laravel Livewire is missing to be perfect... A state manager
- Inertia and its approach
- Livewire: more all-terrain?
- Alpine vs Vue: What I'd Like to See
- Communication between components: the real dilemma
- Comparison with other technologies: Vue and Flutter
- What is a state manager?
- Why does Livewire need this?
- How could it be implemented?
- Conclusion: Who wins?
Philosophy of use: Which one to choose?
There is no magic tool; everything depends on the needs of your project:
- Inertia.js (Vue/React/Svelte): It is ideal when you are looking for extremely rich client-side interactivity. For my own academy, I chose Vue because the Node.js ecosystem and its browser extensions are unrivaled.
- Livewire: It is unbeatable in development speed for administration panels (dashboards) and complex forms, as it allows you to program almost everything in PHP.
Implementing DataTables in Livewire
Livewire stands out for being concise. Being tied directly to the server, the communication is transparent. In my implementation, I use an organized component system to keep the code clean.
To make the DataTable reusable, we extend from a custom class called 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'; }- Abstract Classes: We use inheritance to define common behaviors (like sorting) without repeating code in each listing.
- Query Scopes: In Laravel, we implement "scopes" in models to centralize filters. This allows us to leverage the power of Eloquent implicitly.
- Elegant Filters: Instead of using multiple if conditionals, we use the when() method. This makes the code more readable and efficient, activating filters only when requested by the user.
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 ]; }In Vue, it is simply a wire:click to the server:
<button wire:click="sort('{{ $key }}')" class="flex items-center gap-1">Source code at:
https://github.com/libredesarrollo/curso-libro-livewire-4
Implementation in Inertia.js (Vue 3)
With Inertia, the server logic is similar, but the client side becomes more "tortuous". Since there isn't a bond as strong as in Livewire, we must manage the communication between Vue components manually.
To make sorting reusable in Vue, we use Composables. These allow us to share reactive logic, but the event flow is more complex:
- A child component (the table header) emits an event when clicked.
- The parent receives the event and updates the parameters.
- Inertia performs a GET request to the server to refresh the data without losing the page state.
That is, from:
resources\js\components\shared\DataTable\DataTableHeader.vue
@click="handleSort(key)"We emit to the parent
resources\js\components\shared\DataTable\DataTable.vue
<DataTableHeader *** @sort="handleSort"And to the other parent:
resources\js\pages\dashboard\post\Index.vue
<DataTable *** @sort="applyFilters">Where in Livewire, being Livewire components, the communication is direct; additionally, the Vue implementation is more complex due to the composables and the rest of its structure.
Source code at:
https://github.com/libredesarrollo/curso-libro-laravel-inertia-3/
⚔️ Conclusions for the Datatable
This is where Livewire (with Flux) wins by a landslide compared to the standard Inertia setup:
- Livewire + Flux: Flux is a perfect marriage between Blade, Tailwind, and Laravel. It gives us ready-to-use components (tables, buttons, calendars, breadcrumbs) with clean syntax. We don't have to worry about passing complex props; we simply define arrays and everything works.
- Inertia: By default, the components that come in Inertia starters tend to be too atomic or insufficient. You often end up implementing DataTables manually, which adds maintenance burden and unnecessary logic to your solution.
Aesthetically, you can achieve the same result with both technologies, but Livewire is much more direct. As the saying goes: "the best programmer is not the one who solves the most problems, but the one who avoids them." All the event and communication logic that Vue/Inertia requires feels like unnecessary "noise" when what you are looking for is pure functionality tied to the server.
Author's Note: If you want to dive deeper into these implementations, remember that this content is part of my full Laravel courses and books. You can find all the material, source code, and detailed guides in my Academy.
Ultimately, for this section, A HUGE POINT FOR LIVEWIRE:
- Livewire: 1
- Inertia: 0
Events, Nesting, and Communication between components
After comparing the performance of DataTables, where Livewire emerged as the winner due to its simplicity and lesser amount of logic, in this section, we will analyze the creation of forms step by step.
The Concept of a Component
A component is a modular and reusable unit of work. In this exercise, we are looking for total modularity. For example, the contact creation form is an independent component that can be consumed:
- From a "step-by-step" main page.
- Within a blog post.
- As a modal in another section.
Both Livewire (with its "vitaminized components") and Inertia (based on Vue 3, React, or Svelte) use this concept as a central axis.
Backend Implementation (Laravel)
On the Laravel side, there are no major differences, as both technologies rely on "base Laravel":
- Livewire: Manages validations directly in the component class or through form objects. Upon submission, rules are applied and the typical CRUD is executed.
- Inertia: Although the controller is practically identical, we usually delegate validations to a Form Request or a separate class.
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;Frontend Management and Reusability
This is where we truly measure the ease of adapting and reusing components.
The Success of the Automatic Layout
Both technologies achieve something very clever: Layout detection.
- Inertia: If a component is consumed via a route, it loads the defined layout; if it is consumed as a child component within another Vue page, the layout does not load, avoiding duplication.
- Livewire: The same thing happens. If it is invoked as a component within another Blade view (for example, in a @livewire('show-contact')), the main layout is not rendered.
This is a huge advantage over base Laravel, where an @extends in Blade would load the master template regardless of how the view is consumed.
Reactivity and Communication: The Critical Point
Complexity in Livewire
In Livewire, reactivity can sometimes be confusing due to the coupling between PHP and JavaScript.
- Events: To communicate a child with the parent, we use events. However, when reading the code, it is sometimes unclear who is listening to that event if you don't have the component tree in mind.
#[On('stepEvent')] public function stepEvent($step) { $this->step = $step; } *** <flux:button wire:click="$dispatch('stepEvent',[1])">Back</flux:button>
- Alpine.js and Interaction: Livewire uses Alpine.js for client-side reactivity. Although powerful, mixing wire:model, wire:get, and Alpine objects can create visual and mental "noise" for beginners.
<div x-data="{ active:$wire.entangle('step') }" class="flex mx-auto flex-col sm:flex-row">
- Limitation: A negative point is that if a child component has not been rendered (due to a conditional), it cannot easily receive events from the parent, which limits fluidity.
Clarity in Inertia (Vue 3)
Inertia offers a more modular and predictable experience:
- Separation of concepts: The controller returns data and Vue handles 100% of the client logic.
- Props and Events: When using Vue, communication via props and emits is standard. If you see an event, you know exactly where it is defined in the JavaScript code.
<ContactCompany :contactGeneralId="contactGeneral.id" @back-step-event="backStep" v-if="$page.props.step == 2" :contactCompany="contactGeneral.company" />
Real Reactivity: Vue is extremely smart. Even if a component is under a v-if, at the moment of rendering, communication and state are synchronized naturally; that is, we can communicate the child component to the parent EVEN IF at the moment of loading the page the child component is NOT loaded, as in the previous code block.
- In Livewire, this is NOT possible; if a child component is NOT loaded when the page loads, then you CANNOT send messages from the child to the parent:
<livewire:contact.company :parent-id="$contactGeneral->id" /> *** company.blade.php // It doesn't work because its children are not rendered by the @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); } }
Verdict: Which is better for a "Step-by-Step"?
In this specific exercise, Inertia (Vue) takes the point for the following reasons:
- Easier Debugging: Since the server and client are not so tightly coupled in every small interaction, it is easier to analyze where something fails.
- Error Handling: In Inertia, the Vue form object handles errors locally and modularly. In Livewire, if not managed well, the global error object can cause conflicts when nesting multiple forms.
- Expressiveness: Vue's syntax for handling complex states (like the current step of a form) is more natural and less prone to unnecessary "roundtrip" errors to the server.
- Summary: While Livewire won in the DataTable for its implementation speed, Inertia wins in complex and nested components for its robustness and real reactivity.
In short, even though I am TEAM Livewire, Inertia deserves its big point in this module:
- Livewire: 1
- Inertia: 1
To Do List
We are going to compare two implementations of a To Do List application: one developed with Inertia.js (using Vue) and another with Livewire (supported by Alpine.js). The application is simple yet functional: it features the typical CRUD (Create, Read, Update, Delete) centralized in a single window to evaluate which development workflow is more efficient.
Features and User Experience
In both versions, we have inline editing. For example, when selecting a task, we can edit it and the changes persist directly in the database.
- Task Management: Checking and unchecking items, creating new tasks, and deleting them.
- Design and Styles: The design varies slightly. In one, I used predefined components, while in the other, I styled it with Tailwind CSS from scratch.
- Interactivity: Both feature a Drag & Drop system for reordering tasks and real-time (online) searching.
Technologies and Dependencies
The main focus of this comparison is to evaluate client-side development: Vue.js versus Livewire + Alpine.js.
- Backend: In both cases, it is very similar, based on classic Laravel controllers.
- Frontend: We use Sortable.js as an external dependency for reordering.
- Project Origin: The Livewire version was born from an application we first made 100% in Alpine.js (available in my books and courses), to then demonstrate how to adapt and integrate it with the server transparently.
Server Analysis (Backend Logic)
Implementation with Inertia
In Inertia, we work with traditional controllers. We have methods to get the list, create, and update. We include sorting logic using a foreach that updates the position of the received IDs, and we always filter by the authenticated user to guarantee security.
Implementation with Livewire
Here we use an "all-in-one" component. We define validation rules, the mount method to load data, and the save, delete, and setPositions functions. The model is straightforward; it is a simple structure that works the same for both technologies.
The Client Challenge: JS vs. Livewire + Alpine
The real challenge lies on the client side.
- Alpine.js: We define an x-data block. In this project, as it involves complex logic, I extracted it into a separate function. We use the @script directive to load the Livewire JavaScript correctly and avoid rendering errors. The best part is the direct communication: we use Livewire events to dispatch actions to the server transparently, without the need to configure manual routes or use Axios/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 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"> <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">Completed</span> </template> <template x-if="!completed(t)"> <span class="text-orange-600 text-sm font-medium">Pending</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: For many, it is more elegant as it is a full framework. We import components, define props for the task list, and handle the form with the form.post helper. Although it is more structured and maintains a clear separation between client and server, it requires defining routes in web.php and managing an automatically generated JS route file.
<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"> <div class="mb-4 flex items-center justify-between"> <h3 class="text-2xl font-bold">App To Do</h3> <Button variant="destructive" size="sm" @click="removeAll" class="gap-2"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4"> <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg> Delete All </Button> </div> <form @submit.prevent="create" class="mb-2 flex gap-2"> <div class="flex-1"> <Input :class="{ 'border-red-500 bg-red-50': errors.name && todoSelected == 0, }" v-model="form.name" placeholder="What needs to be done?" /> </div> <Button :disabled="form.processing">Send</Button> </form> <InputError v-if="todoSelected == 0" :message="errors.name" /> <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 dark:bg-gray-800"> <span class="drag-handle mr-2 cursor-grab text-gray-400">::</span> <button @click="status(element)" class="focus:outline-none"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" :stroke="element.status == '1' ? '#10b981' : 'currentColor' " viewBox="0 0 24 24" stroke-width="2" class="size-6 transition-colors"> <path v-if="element.status == '1'" stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /> <path v-else stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /> </svg> </button> <div class="ml-3 flex-1"> <span v-if="!element.editMode" @click="element.editMode = true" class="block w-full cursor-pointer" :class="{ 'text-gray-400 line-through': element.status == '1', }"> {{ element.name }} </span> <Input v-else v-model="element.name" @keyup.enter="update(element)" @blur="element.editMode = false" auto-focus /> <InputError v-if="todoSelected == element.id" :message="errors.name" /> </div> <button class="ml-2 opacity-0 transition-opacity group-hover:opacity-100" @click=" confirmDeleteActive = true; deleteTodoRow = element.id; "> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ef4444" class="size-5"> <path fill-rule="evenodd" d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5 0l.5 8.5a.75.75 0 1 0 1.5 0l-.5-8.5Zm4.785 0a.75.75 0 1 0-1.5 0l-.5 8.5a.75.75 0 1 0 1.5 0l.5-8.5Z" clip-rule="evenodd" /> </svg> </button> </li> </ul> </div> </div> </WebLayout> </template>
Conclusion: Which one to choose?
If we compare lines of code, both implementations are very close (around 250 lines).
- Inertia (Vue): It is more structured and robust, but the cost is writing more code and managing more files.
- Livewire (Alpine): It is simpler and more elegant in its integration. Everything feels more "mixed" (Livewire, Alpine, and third-party plugins), which is precisely the magic of this ecosystem: the speed of development.
For this occasion, I give a tie to both:
- Livewire: 2
- Inertia: 2
Blog
In this new comparison, we are going to analyze the development of a Blog (listing and detail with filters). Recall that we have already evaluated other modules:
- DataTables: Livewire won for being more reactive and easier to maintain.
- Sanitization Components (step by step): The point went to Inertia, as being client-side components, Vue or React outperform Alpine.js in power.
- Todo List: We declared a technical tie.
The SEO Challenge in SPA Applications
A critical point when developing a blog is SEO. Traditionally, client-side technologies like Vue, React, or Svelte have issues because content is loaded asynchronously via JavaScript, and Google does not always correctly index what is not in the initial HTML.
However, Inertia.js solves this with SSR (Server Side Rendering). By using the Inertia::render function instead of the traditional Laravel view(), the server processes the component and sends the data (title, metadata, and content) already rendered. This allows Google to read the content as soon as the page loads, without waiting for the client-side JavaScript (such as the onMounted method) to execute.
For its part, Livewire does not have this problem, as it is a technology born on the server and renders HTML directly from the start.
Performance and Resource Loading
A blog must be lightweight. In my personal experience, I try to ensure that the page is not blocked by external resources.
- Inertia: Loads the Vue and Inertia core, which adds some JS weight, although it is manageable.
- Livewire: Loads its own JavaScript script.
In my case, even when using Livewire, I prefer to exclude its scripts in parts that do not require reactivity and use Alpine.js for minimal details (like highlighting a button). I load all non-essential JS (advertising, secondary scripts) asynchronously so that the user experience is instantaneous.
Development and Code Complexity
Comparing the code of both for the Blog module:
- Server Logic: It is identical. Both leverage the power of Laravel (like Eloquent Scopes for filters), so there is no clear winner here.
- Code Length (View/Component):
- Livewire: Wins in simplicity. The show.blade.php file has about 87 lines containing all the logic and design.
- Inertia: The Vue component goes up to about 102 lines, to which the controller code must be added.
Even though Inertia is a bit more extensive, it gives us access to the entire Vue plugin ecosystem, which is an implicit advantage.
Conclusion: Who takes the point?
Although a blog is a simple development that does not always require such "pro" technologies, it is important to know that both Inertia and Livewire are perfectly capable of handling it.
Inertia has shown it can compete in the SEO field thanks to SSR, and Livewire remains the king of development speed in the Laravel ecosystem. Because both solve the problem excellently with similar levels of complexity for this module:
Result: Tie.
With this, the global score stands at 3 to 3. Both frameworks prove to be all-terrain tools, and the choice will depend more on your personal preference for the ecosystem (Vue/React vs. Blade/Alpine) than on the limitations of the technology.
- Livewire: 3
- Inertia: 3
Shopping Cart
Let's move on to another of the modules, and I think it will be the last one we work on: the cart. Its operation is based on my blog's philosophy, where I handle different types of publications: regular posts, courses, books, and ads.
For "advertising" type items (which act as products in this context), we use the shopping cart. When trying to acquire one, a modal appears that allows the product to be added. What is truly interesting here is the communication between components, similar to what we explained in the "step-by-step" module.
CRUD Structure and Operation
If we analyze the structure in the library, we have the main Cart component, which is used for the single page (equivalent to what we handle in Inertia). Inside this, we use another component called CartItem.
Item Logic:
The CartItem is what actually manages the project's CRUD. An item can be modified to increase its quantity or deleted (by setting the quantity to zero).
- Frontend Management: We use an array where the post_id serves as a unique reference. This avoids having to iterate through the entire cart every time we want to know if a product already exists; we simply query by its identifier.
- Backend Management: In the Inertia controller, the logic remains identical. There is no clear "winner" on the backend, as both use the Laravel base; it only changes whether you define the logic in a traditional controller or in a Livewire component class.
<?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');
}
}
};
?>
<div class="max-w-2xl mx-auto py-8 space-y-6">
<flux:heading level="1" class="text-center">Shopping Cart</flux:heading>
@if ($type == 'list')
<flux:card>
@forelse ($cart as $c)
@livewire('shop.cart-item', ['postId' => $c[0]['id']])
@empty
<flux:text class="text-center text-zinc-500 py-8">
Your cart is empty
</flux:text>
@endforelse
</flux:card>
{{ view('pages.shop.partials.shop-cart', ['total' => $total]) }}
@else
<flux:card>
<div class="flex items-center gap-4">
<div class="flex-1">
@livewire('shop.cart-item', ['postId' => $post->id])
</div>
<flux:button
variant="primary"
wire:click="dispatch('addItemToCart', { post: {{ $post->id }} })"
icon="shopping-cart"
>
Buy
</flux:button>
</div>
</flux:card>
@endif
</div>
Reactivity: Livewire vs. Inertia
This is where we find Livewire's "Achilles' heel."
The Livewire problem:
In Livewire, communication is not automatic. If you modify a CartItem, you must notify the parent component to reload the total or change the interface (for example, showing the checkout panel).
Unlike JS frameworks, Livewire does not have real reactivity, but rather a "simulated" one through server requests. This makes synchronization between components more manual and, sometimes, prone to visual glitches if events are not managed well.
The Inertia advantage (Vue/React):
In Inertia, when using Vue, reactivity is natural. You don't need to manually emit events to update the total; you simply perform the operation (a router.post) and, as the state updates, all components that depend on those data refresh "magically." It is much cleaner and more efficient on the client side.
Events in Livewire
What I like least about Livewire is its event system (dispatch). Although powerful, it becomes extremely abstract.
- Lack of reference: You can emit an event, but it is not always clear which component is listening to it. If you return to a project months later, you lose track of where the function responding to that event was defined.
- Low coupling: It is good because it allows any component to communicate, but it is bad because it generates a disorganized structure (a "free-for-all") where events can be triggered from anywhere without a clear hierarchy.
Code and Organization Comparison
At the code line level, both are very similar (around 160 lines for the most complex components).
What I like about Livewire:
I think it is better structured in terms of file organization. For example, pages are housed in their own Pages folder, separate from reusable components. Livewire guides you more toward an organized structure by default, similar to how Laravel organizes models and controllers.
$this->dispatch('itemAdd');This Is NOT a Component, IT'S a View, IT'S NOT Modular: Laravel Inertia vs Livewire

What we have is NOT a component in Laravel Inertia. At least, it's not a component per se in the context of our application.
What the hell am I talking about, you might ask?
I'll give you a little context here. Obviously, this is—so to speak—a personal interpretation, once one has developed applications using Laravel Inertia, which is precisely what we're looking at.
Laravel, among other technologies, is associated in some way with certain concepts, and one of them is that of a component. But, again, this is mostly a personal interpretation. I'll tell you why I consider this not a component, but simply a view:
<contact-layout>
<general-form :errors="errors" :contactGeneral="contactGeneral"/>
<company-form :contactCompany="contactGeneral.company"/>
</contact-layout>
But… how is it not a component?
The first thing you could tell me is:
“Look, that’s obviously a component! It’s a damn component in Vue! Aren’t you seeing it, for God’s sake?”
And yes… the code above is a .vue file. We have a component we're importing, called Contact. It obviously has its own <template> and <script>. So, you can rightly say:
“That’s a component in Vue.”
And yes, that's correct.
But...
To understand my point of view a little bit (which, as I said, I'll compare later), it's important to see how sister technologies like Livewire and Inertia behave. And as odious as comparisons are, they must be made.
What do I mean by this? What we're using in Inertia is, in fact, a view.
Let's remember that one of the main features we have in Laravel Inertia is the use of Vue, or rather, the ability to use Vue. Because we can even return a Blade view instead of a Vue view. It's that simple.
From the ground up: components in Laravel
To make my point even clearer, let's get back to basics. In Laravel, components were typically used to define, for example, buttons. That is, simple elements that didn't require additional logic.
We could have something like:
- Anonymous components, more organized than a single view.
- Classy components, which did require some logical initialization.
So, with those concepts clear, I ask you:
What do you think we have in the code above?
Because if you look closely, in Inertia we're forced to manually pass the data for the component to work. This is because, as they are independent technologies, there's no deep integration, and there probably never will be.
Comparison with Livewire
In the Livewire example:
@livewire('contact.company', ['parentId' => $pk])We simply pass it an identifier to build the component. Using that ID, the related class is internally searched for:
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');
}
—which in this example could be ContactCompany—. Livewire automatically executes the mount method, initializes the component, and you don't have to worry about manually passing it all the data.
Whereas in the case of Inertia, in addition to the ID, you have to pass it the class instance, which is usually constructed in the edit method.
So, in our Inertia component (that .vue file we showed earlier), we're simply importing a view and passing it data to initialize it manually. There's no Vue component instance as such with a server-controlled lifecycle.
That's why I don't consider it a component as such, but simply a rendered view with data.
What the HELL#%!& is Laravel Inertia for?
This is a question I've been asking myself for months and that I want to share with you, including a little context about my projects and experiences.
The projects I usually work on typically have two parts:
- Administrative: control panel, dashboard, CRUD, internal management.
- End-user: blogs, product sales, courses, books, reservations, etc.
This applies to any topic: selling cars, tickets, caps, renting houses or hotels... there's always an administrative part and another part visible to the end-user.
SPA and SEO: a complicated relationship
A significant problem with SPAs (Single Page Applications) is that they don't always rank well in search engines. Google, for example, loads dynamic content in chunks and often doesn't "read" the information correctly, which makes SEO difficult.
That's why, for web pages we want to rank (hotels, products, blogs), the classic approach is still more recommendable.
Inertia: Vue instead of Blade
The main feature of Inertia is that it returns a Vue component instead of a Blade. For example:
public function create()
{
$categories = Category::get();
return inertia("dashboard/post/Save", compact('categories'));
}That's practically 90% of what Inertia offers. Unlike Livewire, it doesn't provide direct interaction with the backend using component events or methods.
For example, in Livewire we can have a button that executes a component method directly:
<x-button class="flex-shrink-0" wire:click="tagSave">
{{ __('Set') }}
</x-button>And call component methods directly:
public function tagSave()
{
// dd($this->tags[$this->tag_selected]);
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 for dashboards: not the best option
For administrative modules, such as a dashboard, I wouldn't recommend Inertia.
- Vue/Inertia is more intended for the end-user, where animations, interactivity, and visual experience are key.
- The backend is usually more functional and simple: less style, fewer animations, more efficiency.
In contrast, Livewire is more efficient for dashboards:
- Everything is still Blade/Laravel.
- Component methods are called directly without the need for additional routes or Axios requests.
- Better scalability and modularization for CRUD operations and complex actions in the dashboard.
Rest API + Laravel: the ideal option nowadays
Today, all web applications have their mobile equivalent: Udemy, Duolingo, Gmail, etc.
If I had used Inertia for my academy website, I would have had a significant problem:
- To create the mobile app in Flutter, I would need to duplicate every Inertia controller to generate the Rest API.
- Every change on the web would have to be replicated in the mobile API.
- This generates redundancy and complications in the application logic.
That's why the best practice is:
- Laravel + Rest API (robust and reusable backend).
- Vue/React/Flutter (web or mobile frontend).
- Livewire only for dashboards where fast interaction is needed without duplicating logic.
- Inertia is useful if you want to replace Blade with Vue and maintain a web project that is quick to develop.
- It is not ideal for administrative dashboards or when you plan mobile applications that consume the same logic.
- For scalable and cross-platform projects: Laravel + Rest API + separate frontend (Vue, React, Flutter) is the most recommendable combination.
- Livewire is still excellent for dashboards and CRUD operations where direct integration with Blade/Laravel is an advantage.
The ONLY thing Laravel Livewire is missing to be perfect... A state manager
Inertia and its approach
I've already mentioned in other videos some things that don't quite convince me about Inertia. I consider it a useful technology if you just want to work directly with Vue, which is already quite a lot. But beyond that, I don't think Inertia has anything special that would make you say, "This is perfect for this case," unless, again, you want to use Vue.
Livewire: more all-terrain?
On the other hand, Livewire seems more of an all-rounder to me compared to Inertia. It's not like you'd say "I just need to create this project with Alpine," but it does stand out for the flexibility it offers.
That's enough to summarize what I've discussed in other videos. However, I wanted to touch on one aspect that, for me, would make a key difference and make Livewire almost perfect.
Alpine vs Vue: What I'd Like to See
I mentioned earlier that I would love for Livewire to use Vue instead of Alpine, because Alpine sometimes seems a bit cumbersome to me. It works for simple things, but when you try to scale, it can become a bit annoying. Maybe it's just my perception because I've worked with Vue a lot more, but I still wanted to mention it.
This all came about from a comment I received recently. As always, when you talk about a technology, its proponents come up—which is fine, everyone has their own point of view. One of those comments caught my attention: the person said they didn't find Livewire complex to work with.
And they're right; it's all a matter of perception. I say it's complex because I've worked with many technologies, and I know how to implement the same logic in other tools where it's simpler. And the point I always make is: communication between components in Livewire can be a nightmare.
Communication between components: the real dilemma
When you have multiple nested components—not just parent and child, but also grandchildren or great-grandchildren—as we showed in the Livewire Basics course, it becomes very cumbersome. You have to pass messages through events, register listeners in the parent, and then trigger events from there. All of this can be very abstract and unclear to another developer coming into the project.
So I started thinking: how do other technologies do this?
Comparison with other technologies: Vue and Flutter
I also primarily work with Vue and Flutter, highly modular and component-based technologies (or widgets, in the case of Flutter).
In both, parent-child-grandchild communication isn't a problem, but there comes a point where the hierarchy grows and things get complicated. At that point, the recommended approach is to use a state manager.
In Flutter
Flutter has many options:
- Provider
- Bloc
- Redux
- Riverpod
I've worked primarily with Provider and some Redux. Bloc never convinced me, but it's a valid and widely used option.
In Vue
In Vue, we also have state managers:
- Vuex
- Pinia, que es más moderno y mucho más intuitivo.
Personally, I prefer Pinia for its simplicity.
What is a state manager?
A state manager is simply a layer you configure over your application to share and modify data between components.
For example, suppose you have the user's name in the main layout, and you want to modify it from a component three or four levels below. With a state manager:
- You place the name in the store.
- Any component can access that data (getter).
- And can also modify it (setter).
The change is automatically reflected throughout the component tree.
This can be done without having to propagate events from great-grandchild to grandchild, from grandchild to child, from child to parent, and so on, as is the case in Livewire.
Why does Livewire need this?
Livewire doesn't have a state manager. And that makes things very complicated when your project grows.
Imagine passing data from a grandchild component to the main layout, passing through the entire intermediate hierarchy... It's simply unsustainable.
That's why state managers are emerging in Vue, React, Angular, Flutter, and other frameworks, to avoid this mess. And that's why I think Livewire urgently needs it too.
How could it be implemented?
Ideally, Laravel Livewire could incorporate something like:
- A special component that functions as a store.
- Or an additional layer that serves to centralize the state.
It doesn't necessarily have to be a visual component, but rather something that can be configured and allows for clearer information sharing across all parts of the hierarchy.
Conclusion: Who wins?
At the start of this development, I thought Livewire would win clearly, but after evaluating reactivity, the result is a technical draw.
- Inertia (Vue/React): Wins on the client side. It is unbeatable for complex interfaces with a lot of communication between components, events, and fluid reactivity.
- Livewire: It is my favorite for 90% of cases. It is excellent for CRUDs, dashboards, and applications where there isn't such deep component nesting. It is the most "generalist" and productive tool for a Laravel developer.
In the end, the score is 4 to 4. It's not that one technology is better than the other, but rather that you should choose the one that best suits your project. For the "presentation" part to the client, I prefer Vue's reactivity; for internal management and development speed, I'll stick with Livewire.
- Livewire: 4
- Inertia: 4