Features of a Livewire project: Livewire 4, volt, flux, Alpine...

Video thumbnail

Before starting to develop our application in Livewire, once the project in Laravel Livewire has been created, I want to take a short break to tell you what we have in a project with this framework.

In a nutshell, we can say that Livewire are “boosted” Laravel components. That is, with these components we can perform multiple operations that would normally require much more code or additional configurations.

When you start a project with Laravel Livewire, especially from version 12 onwards, you quickly discover that you are not working with “traditional Laravel,” but with something that—as happened to me the first time—feels like boosted Laravel components. In this article, I explain, from a practical perspective, what the real characteristics of a modern Livewire project are, how it is built, what it includes, what changes compared to previous versions, and how to get the most out of it.

We're going to talk about the most important aspects we have when creating a project in Laravel Livewire starting from version 12 of Laravel, which, as we mentioned before, unlike previous versions, no longer includes the functionalities we had in previous versions with Jetstream.

What does Livewire offer us?

Creating modern applications today is a challenge. Not only must we master the Backend (where Laravel is the undisputed king), but also the presentation layer or Frontend. Traditionally, this implied using frameworks like Vue or React, which forced us to separate our development into two distinct worlds, increasing complexity and maintenance.

This is where Livewire comes in: a full-stack framework for Laravel that gives us the benefits of reactivity (very similar to what Vue offers) while keeping us 100% integrated into the Laravel ecosystem. It is, literally, the best of both worlds.

Livewire is a complete framework for Laravel that simplifies the creation of dynamic interfaces without leaving the comfort of Laravel; in short, it allows us to use schemas similar to those of Vue and React directly in Laravel.

We can work with schemas similar to those of Laravel and Vue, but in a more direct and simple way; Laravel development is strongly linked to the use of components; the same Laravel components but with important additions from Laravel Livewire, which allows scaling the use of components enormously:

  1. Linking via the routes file.
  2. Simple communication based on actions, events, and functions between the client and the server.
  3. Adds features like pagination, file uploading, query string, among others.

In short, with Livewire, we can do the same things we do with Laravel and its controllers, but in a simpler way where we can use components and reuse pieces of code that Livewire offers us to make server-client communication very straightforward.

Reactivity without complications

For me, the best part of Livewire is the power of its events. In traditional development or with APIs, performing an action like "cloning a post" would require:

  • Creating a route.
  • Implementing a call with Axios.
  • Handling the response on the client side.

With Livewire, this is reduced to a wire:click (or similar) attribute that directly calls a server method. It's pure magic and simplifies the workflow in an incredible way.

Besides that, depending on how you decide to install Laravel Livewire, you can enable options that already come for free such as:

  1. Authentication system with registration and credential recovery.
  2. Profile view with user upload.
  3. Team management through teams.

In essence, Livewire is not a framework; you can see it as just another package that adds extra functionalities to some Laravel elements that turn it into a scaffolding or skeleton for our applications.

Use Cases: SEO and Dashboards

Livewire is extremely versatile. On my own blog, for example, I use a strategic combination:

  • Administration (Dashboard): I use Livewire to take advantage of development speed and reactivity in forms and tables.
  • Public part (Blog): I use Laravel directly because, as it's rendered on the server, the content is easily indexable by Google, unlike some heavy JS implementations or using Livewire.

A constantly evolving ecosystem

Livewire and Laravel change fast. Livewire releases major versions every so often, and Laravel does so annually. In this course, we will not only learn the theory but also modern implementation:

  • Single-file components: We will see how Livewire is migrating towards a structure similar to Vue's Single File Components, where logic and HTML can coexist to improve development speed.
  • Integration with Alpine.js: For those interactions that don't need to travel to the server, Livewire relies on Alpine.js, a lightweight JS framework that we will also cover.

What Livewire allows

With Livewire components we can:

  • Perform view routing: instead of a controller returning a view, we can do it using a component.
  • Use dynamic properties, similar to Vue's v-model, to reactively link data between the frontend and the backend.
  • Work with query strings to keep information in the URL and synchronize it with the component's state.
  • Handle file uploads easily.
  • And many other operations that we will see in practice.

I don't want to get too far ahead, because some functions are more technical, and it's better to explore them directly while building our application.

The important thing is to take away the idea: Livewire gives us improved Laravel components, with many ready-to-use functionalities, which allows us to develop dynamic applications without leaving the Laravel ecosystem.

Why choose Livewire for your Laravel project?

Livewire has positioned itself as a perfect intermediate solution between traditional Blade and SPA frameworks like Vue or React. It's ideal for those who want reactive interfaces without having to set up an entire modern JavaScript infrastructure.

In my first Livewire projects, one of the things that surprised me the most was being able to perform instant actions with wire:model, wire:click, or even manage Query Strings without writing a single line of JS. You literally connect a property to the view and the state flows.

Main advantages to highlight:

  • Elimination of complex JS for most interactions.
  • 100% native integration with Blade.
  • Reusable and nested components.
  • Lazy loading, instant validation, file uploads.
  • Ideal for panels, dashboards, CRUDs, and internal tools.

Essential elements of a Livewire project

Folder structure and components

A Livewire project in Laravel 12 is based on the idea that every part of your interface is a component. This includes forms, dashboards, sidebars, profiles, and any element that needs logic and a view.

In my first steps, I noticed that, unlike previous versions, the direct Livewire dependency no longer appears in composer.json: it now arrives through packages like Volt and Flux, which changes the project's architecture.

If you review the structure of a freshly installed Livewire project, you'll notice:

  • /app/Livewire → where the components live
  • /resources/views/livewire → Blade views of each component
  • /resources/views/components → auxiliary Blade components
  • /routes/web.php → routes that can point directly to Livewire components

It's common to see routes like:

Route::get('/dashboard', \App\Livewire\Dashboard::class);

Key dependencies: Volt, Flux, and more

In Livewire 12+, the ecosystem is reinforced with:

Volt

Volt allows building logic and view in a single file, something that feels almost like Svelte or Vue Single File Components, but directly in Blade.

A typical example:

<?php
use function Livewire\Volt\{state};
state(['count' => 0]);
$increment = fn () => $this->count++;
?>
<div>
  <h1>{{ $count }}</h1>
  <button wire:click="increment">+</button>
</div>

Volt is not one of my favorite technologies, as I don't like mixing logic with presentation, and in Livewire 4 we will even have a third way to create components in Livewire; even so, it is a completely valid structure.

Laravel Volt

In recent Laravel Livewire projects, starting from Laravel version 12, creating a project in Laravel Livewire includes other technologies such as:

Volt:

https://livewire.laravel.com/docs/volt

With which, we can create both the logic and the view (blade) in a single file:

<?php
use function Livewire\Volt\{state};
state(['count' => 0]);
$increment = fn () => $this->count++;
?>
<div>
   <h1>{{ $count }}</h1>
   <button wire:click="increment">+</button>
</div>

Laravel Flux UI

We will also see that the auxiliary components we use now are not the same as before; we now use Flux.

Flux, which is nothing more than a library of ready-to-use components for Laravel Livewire:

https://fluxui.dev/

<flux:input
   wire:model="email"
   :label="__('Email address')"
   type="email"
   required
   autocomplete="email"
   placeholder="email@example.com"
/>

Which you can expose to customize:

$ php artisan flux:publish

We also see a more complex structure, such as not having the Livewire dependency in composer.json, but rather the previous packages that import Livewire as a dependency.

Flux is a library of pre-built UI components that Livewire integrates to speed up development; it really has very good components. It is a freemium type, meaning it has free and paid components.

Components like:

<flux:input>
<flux:navlist>
<flux:dropdown>
<flux:menu>
<flux:profile>

For example:

<flux:input
  wire:model="email"
  label="Email"
  type="email"
  required
/>

These are Laravel components with tons of Tailwind classes, easy to extend:

$ php artisan flux:publish

With the command above, you can publish ALL flux components to analyze them.

Data-binding, props, and events (wire:model, actions)

Livewire allows views and logic to stay automatically synchronized.
Examples:

  • wire:model="email" keeps the state between input and component.
  • wire:click="save" executes backend methods without reloading.
  • wire:loading handles loading states.
  • wire:keyup.debounce.500ms optimizes events.

For me, the BEST thing about Livewire is wire:click.

What a Laravel Livewire Project Brings Us

In this section, we're going to talk about the most important aspects we have when creating a project in Laravel Livewire starting from Laravel version 13 with Livewire 4, which, as we mentioned before, unlike previous versions, it no longer includes the functionalities we had in previous versions with Jetstream, although they did include Team management again as an option.

The first thing we see is a couple of links for registration and login. At this point, if you haven't done it yet, create a user; once registered or logged in, you will have access to; create a user:

  • http://livewirestore.test/register

And it will send you to:

  • If you selected team: http://livewirestore.test/team-1/dashboard

  • Without team: http://livewirestore.test/dashboard

Publish the Livewire configuration file:

$ php artisan livewire:publish --config

Or:

$ php artisan livewire:config

Which is a configuration file where you will see the layout used:

config\livewire.php

'component_layout' => 'layouts::app',

I recommend that you review the mentioned file; you will see information indicating that it already injects the Livewire CSS and JS to be able to use the attributes and requests (which we will learn about later):

The view that will be used as the layout when rendering a single component as | an entire page via Route::livewire('/post/create', 'pages::create-post'). | In this case, the content of pages::create-post will render into .

You can create your layouts for other modules and override them in the configurations, although I recommend that you don't create them for now:

$ php artisan livewire:layout

And you can see that, at the path:

  • resources\views\layouts\app.blade.php

You will see the app layout:

<x-layouts::app.sidebar :title=" ?? null">
    <flux:main>
        {{  }}
    </flux:main>
</x-layouts::app.sidebar>

Which internally uses other components at the same level in an "app" folder:

  • app
    • header
    • sidebar

If, for example, you see the component:

resources\views\pages\settings\⚡profile.blade.php

You will see several interesting things, such as the single-file composition (HTML and PHP in the same file), anonymous classes, and the use of PHP attributes which are new since version 8:

new #[Title('Profile settings')] class extends Component {
    use ProfileValidationRules;

And the parameter passing:

resources\views\pages\settings\⚡profile.blade.php

Title('Profile settings')

resources\views\layouts\app.blade.php

<x-layouts::app.sidebar :title=" ?? null">

Previously, to pass parameters it was:

->layoutData(['title' => 'Teams'])

And best of all, NO route exists for this page:

Route::view('/', 'welcome', [
    'canRegister' => Features::enabled(Features::registration()),
])->name('home');
Route::prefix('{current_team}')
    ->middleware(['auth', 'verified', EnsureTeamMembership::class])
    ->group(function () {
        Route::view('dashboard', 'dashboard')->name('dashboard');
    });
Route::middleware(['auth'])->group(function () {
    Route::livewire('invitations/{invitation}/accept', 'pages::teams.accept-invitation')->name('invitations.accept');
});

And you can see from the dashboard, in the navbar, an option for the profile:

  • http://livewirestore.test/settings/profile

Why the ⚡ emoji?

You might wonder about the lightning bolt in the filename. This small detail has a practical function: it allows Livewire components to be instantly recognizable in the file tree and your editor's search results. Being a Unicode character, it works perfectly across all platforms: Windows, macOS, Linux, Git, and your production servers.

The emoji is entirely optional, and if you don't find it comfortable, you can disable it completely in your config/livewire.php file:

'make_command' => [
    'emoji' => false,
],

Single File Components (Livewire Volt)

Another novelty that might be striking is the component structure. Traditionally, we had a class in PHP and a view in Blade. Now, with Volt, everything can be united in the same block:

  1. Functional Logic: At the top, we have a PHP block (sometimes identified with an emoji in the editor) where we define properties and functions.
  2. Dynamic Attributes: We use PHP 8 attributes (like #[Layout]) to pass data directly to the layout, such as the page title.
  3. HTML: Right below, we have the Blade code.

It's a very powerful mix that's reminiscent of Vue components but processed entirely on the server. Since everything is in the same file, we no longer need a render() function, as Livewire assumes the HTML below is what should be displayed.

Blade, Blade Components, Layouts, and Flux

As you can see, x-layout-app corresponds to a component in base Laravel:

resources/views/components/layouts/app.blade.php

<x-layouts.app.sidebar :title=" ?? null">
   <flux:main>
       {{  }}
   </flux:main>
</x-layouts.app.sidebar>

Another thing about the previous code is that components starting with "flux" are the components mentioned at the beginning provided by the Livewire Flux package. There are many throughout the application for different purposes, but their names describe what they do quite well, for example:

<flux:navlist>
<flux:navlist.item>
<flux:heading>
<flux:subheading>
<flux:dropdown>
<flux:profile>
<flux:menu>
<flux:input>

In the end, all of these are just Laravel components with a lot of Tailwind classes, so you can inspect them from the browser; similarly, we will use these components little by little as we progress.

resources/views/components/layouts/app/sidebar.blade.php

<flux:sidebar sticky stashable class="border-r border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">

The rest of the actions created by the Laravel installer include things like changing the password and other user data, closing the account, dark mode, and a bit more. You can find these in the sidebar listed before at the bottom:

  • http://livewirestore.test/settings/profile

Routing, views, and single page (without traditional controller)

A Livewire component can replace a controller.
Classic example:

class Dashboard extends Component {
  public function render() {
     return view('livewire.dashboard');
  }
}

This simplifies the architecture by more than 40% in small/medium projects.

File upload, real-time validation, and lazy loading

Advanced Livewire features that are often highlights of the framework:

  • File upload with prior validation.
  • Instant validation with server rules.
  • Lazy loading for components that shouldn't load on startup.
  • Query Strings synchronizable with internal properties: useful for filters, tables, pagination.

When I implemented file uploads for the first time, I was surprised that I didn't have to write any JS for previews and states.

Analyzing a Livewire project

The route points directly to the component, without going through a traditional controller. The first time I did it, I thought, "wow, this simplifies tons of boilerplate."

But that's just the beginning. The most important thing is to understand how a modern Livewire project is structured, so let's get to that.

The first thing we see is a couple of links for registration and login; at this point, if you haven't already, create a user; once registered or logged in, you will have access to:

http://livewirestore.test/dashboard

Whose view corresponds to:

<x-layouts.app title="Dashboard">
   <div class="flex h-full w-full flex-1 flex-col gap-4 rounded-xl">
       <div class="grid auto-rows-min gap-4 md:grid-cols-3">
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
           <div class="relative aspect-video overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
               <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
           </div>
       </div>
       <div class="relative h-full flex-1 overflow-hidden rounded-xl border border-neutral-200 dark:border-neutral-700">
           <x-placeholder-pattern class="absolute inset-0 size-full stroke-gray-900/20 dark:stroke-neutral-100/20" />
       </div>
   </div>
</x-layouts.app>

As you can appreciate, x-layout-app corresponds to a base Laravel component:

resources/views/components/layouts/app.blade.php

<x-layouts.app.sidebar :title="$title ?? null">
   <flux:main>
       {{ $slot }}
   </flux:main>
</x-layouts.app.sidebar>

The strangest part of the previous code is the component that starts with flux, which are the components mentioned at the beginning provided by the Livewire Flux package. There are many of them throughout the application that serve different purposes, but their suffix already quite well exemplifies what they do, for example:

<flux:navlist>
<flux:navlist.item>
<flux:heading>
<flux:subheading>
<flux:dropdown>
<flux:profile>
<flux:menu>
<flux:input>

In the end, all of these are nothing more than Laravel components with a lot of Tailwind classes, so you can inspect them from the browser; similarly, we will be using these components little by little as we progress.

resources/views/components/layouts/app/sidebar.blade.php

<flux:sidebar sticky stashable class="border-r border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">

The rest of the available actions created by the Laravel installer include actions like changing the password and other user data, closing the account, dark mode, and little else; you have this listed in the sidebar below:

http://livewirestore.test/settings/profile

Jetstream: The Legacy

In its origins, Livewire used Jetstream, which consisted of several features we could use for free that I will list below. However, in the evolution that Livewire has undergone, it NO longer comes bundled with Livewire:

  • What Laravel Livewire Teams allow

    • Teams are a mechanism for grouping users under a particular profile; therefore, a user belongs to one or several teams, which can be the team created automatically when we create the user.
  • Authentication Token Management

    • From the API Tokens option, we can generate authentication tokens that we can use in any other application; for this, remember that we use Laravel Sanctum.
  • Jetstream, Livewire, and Tailwind CSS

    • This package uses by default the CSS framework known as tailwind.css, which, like Bootstrap, offers us a complete set of classes to customize our interface; but, the fundamental difference with the latter lies in the fact that we do NOT have components; we don't have components like cards, buttons, and practically nothing of what appears in the Bootstrap components section.
    • But through these classes, we can build these components; this has its good and bad sides at the same time:
      • The good side is that we can create an interface with a higher level of customization than we can do in Bootstrap, since with Bootstrap's existing components, if we wanted to change them, we would have to overwrite CSS rules.
      • The bad side is precisely the effect obtained from the first point; being a more manual design, we have to spend time creating these components and therefore development is slower.
WHAT I LIKE MOST ABOUT LARAVEL (LIVEWIRE) wire:click

WHAT I LIKE MOST ABOUT LARAVEL (LIVEWIRE) wire:click

Video thumbnail

I want to tell you what I like most about Laravel in general, although, I point out Livewire but for me, even though it is the best thing about Laravel, it is simply this:

wire:click="selectCategodyToDelete({{ $c }})"

For me, there's nothing that surpasses this. Why do I mean this? I also mentioned this a bit in another video, but what are we doing here? What does the previous code mean? Remember, it would be a client-side file. I know it's PHP, but remember that the final rendering is going to be a view. That's transparent to us. That's the final rendering when consumed from the page. In this case, it's a list. It's going to be something like what we have here, just like a pure HTML list. HTML, JavaScript, and CSS. That's what the browser understands. But

What are we doing here? I find it so fascinating. Basically, remember that this is a method we have on the server:

function selectCategodyToDelete(Category $category){
    $this->categoryToDelete = $category;
}

That is, directly, and that's the word here: directly, we're calling a server method.

In other frameworks, or without Livewire, we have to create the route, controller, and a request using Axios or Fetch to simulate the integration we have with Livewire:

wire:click="selectCategodyToDelete({{ $c }})"

Livewire events the key

Of course, with wire click I mean events in general, like submit, it's the same but what changes is the event. I mention the click event since it's the most generic and used, but there are others like submit, change... that you can look up in the official documentation.

The second best thing about Laravel Livewire, its REAL components

As we mentioned earlier, we can invoke something on the server without needing to send forms or manual requests with Axios or Fetch.

With wire:click, it's as simple as this:

wire:click="nombreDelMetodo"

No more creating routes, controllers, methods, or configuring HTTP requests... Everything is much more direct, clean, and modular. And that's precisely what I like most about Livewire.

The second thing I like most are the actual components we can build. That's why I have this screen open: all of this is part of my full Laravel Livewire course/book, which you can get on my academy app or elsewhere.

You can filter by the Laravel section, click on Livewire, and you can purchase it there. It's also available on other platforms, like Udemy, or even in book format.

Livewire: Real Components, Not Just Views

Here we work with real components, not simple views. For example, we have a Blade file, where we reference the corresponding component with @livewire, which is simply a form.

This form has its validations, its logic, and everything else. For example, if I remove a field and submit, you'll see that the validations are executed. You can implement any type of logic in the component, and that's what makes it powerful.

But what I want to highlight isn't just that, but the fact that it's actually a component. If we couldn't do some initialization when defining it, this would be nothing more than a simple view. That's precisely what happens with Inertia.js, and that's why I don't like it so much.

Comparison with Inertia.js

Let's look at an example: the Person component. Here we have its associated class and its view, which we return in the render method. But the interesting thing is that we can execute PHP code when the component loads. Any initialization operation, whatever you want.

For example, in the Contact1 component (the first step), which is Person, we place a dd() or a dump to see that it actually executes. It's not called by a click, but by the component loading when a certain condition is met. In other words, as soon as it loads, server-side code is executed.

And that's what I love about Livewire: its ability to initialize on the server, when the component is mounted.

Controller:

class Person extends Component
{
    ***
    // ****
    function mount($parentId)
    {
        $this->parentId($parentId);
    }


    // **** #[On('parentId')] 
    function parentId($id)
    {
        $this->parentId = $id;

        $c = ContactPerson::where('contact_general_id', $this->parentId)->first();
        if ($c != null) {
            $this->name = $c->name;
            $this->surname = $c->surname;
            $this->other = $c->other;
            $this->choices = $c->choices;
        }
    }

View:

@elseif ($step == 2)
    @livewire('volt.contact.company', ['parentId' => $pk])
@elseif ($step == 2.5)
    @livewire('volt.contact.person', ['parentId' => $pk])

In Inertia.js, however, this doesn't happen. While you can return a component in Vue from Laravel, what you're really doing is returning a view. Nothing is executed on the server side as such. Everything happens on the client side.

Aside from the fact that when we consume a Livewire component through a route or from the view, code is ALWAYS executed on the server, unlike Inertia, where code is only executed on the server WHEN it's a route, and NOT when it's executed from ANOTHER component in Vue.

Although you could perfectly implement an Axios or similar from the Vue component to execute that something (which would have NO advantage compared to base Laravel)... we're talking about which tool makes it easier for us to work with components.

What Does This Entail?

This means that in Inertia, if you want to perform any logic when loading the component, you have to do it from the client, for example, in the Vue lifecycle (mounted), and from there, trigger a request to the server.

Whereas in Livewire, all of this is already integrated. You can work directly from the backend, initializing, querying the database, validating, formatting... whatever you want.

For example, when we pass an identifier to a component, we can also pass all the data, such as the full contact. We do this so that the component is "assembled" correctly. In Inertia, what we do is pass the data as props from the controller, or even from the view, taking advantage of the fact that Vue accepts those props.

It's a slightly strange, but functional, hybrid. However, we don't have that direct server execution as we do in Livewire. In Inertia, we would have to mount the component and then make an additional request if we want initial logic.

So, you can more clearly see the difference between:

  • A real component, as we see it in Livewire, with integrated client and server logic.
  • A Vue component in Inertia, which is actually a view that requires manual logic to communicate with the server.

Things I DON'T like about Laravel Livewire... Its "Complexity"

Video thumbnail

The time has come for me to slam Livewire a little, since I almost always slam Inertia when I compare them. I always make these comparisons because they're supposedly sister technologies: both were released alongside Jetstream, in their first introduction by the Laravel team (I think it was with version 7). From then on, each has taken somewhat different paths.

However, it's still interesting to compare them because when you create a new Laravel project, you can choose between using:

  • Laravel base,
  • Laravel with Inertia + Vue,
  • Laravel with Inertia + React,
  • Laravel with Livewire.

There's no absolute superiority, although—in my humble opinion—Livewire is a bit more of an all-rounder than Inertia. But as I always say: everything has its pros and cons, depending on the type of project you want to do.

The "problems" of Livewire: Complexity and Abstraction

Now, let's get to what I don't like so much about Livewire.

My main criticism is the complexity generated between the Laravel components (Livewire), Blade, and Alpine.js. I feel like everything is starting to overlap.

For example:
You don't know when you're working with Livewire JavaScript and when you're working with Alpine. Here's an example:

<div class="flex" x-data="{ active: $wire.entangle('step') }" class="flex flex-col max-w-sm mx-auto">

When you want to achieve bidirectional communication between client and server, you end up getting into a tangle of x-data, @entangle, get etc. This doesn't happen in Inertia, where you simply define a prop, pass the data and forget about it:

<script setup>
import Layout from './Layout'
import { Head } from '@inertiajs/vue3'

defineProps({ user: Object })
</script>

<template>
  <Layout>
    <Head title="Welcome" />
    <h1>Welcome</h1>
    <p>Hello {{ user.name }}, welcome to your first Inertia app!</p>
  </Layout>
</template>

Here, however, you want to know the value of state, because it's what indicates the "step-by-step" of a form, and when you update the state on the server, you also want to update it on the client. But since you can't read it directly from the get statement, the value is lost, and you end up writing extra logic just to keep things in sync.

More Options? Yes. More Code? Also.

The advantage of Livewire is that it's more tightly integrated with Laravel, giving you more options. But those options sometimes make the code more complicated to maintain.

In Inertia, since everything is directly Vue or React, it's much clearer what happens and where it happens. However, with Livewire, you have logic in Blade, logic in components, logic in Alpine, and logic in PHP. It's hard to keep track of everything.

Another case I found complicated is the communication between parent and child components (and vice versa) in Livewire.

For example, in the "step-by-step" component, which is part of the course and book you can find at Academia, I have a StepEvent that's launched from the child component, and the parent listens to it. The parent is the general component, and the children are:

// parent
class General extends Component
{
  protected $listeners = ['stepEvent'];
}

//child
class Company extends Component
{
    ***

    function back() {
        $this->dispatch('stepEvent', 1);
    }

When you're in one of these steps and the user interacts, the child event triggers the parent event. The parent listens with a @listener and executes the corresponding method.

This can be confusing the first time someone reads it, especially if they're unfamiliar with our code. How do you document something like this? It gets complicated.

Reverse Communication: Father to Son

In the course/book, I also showed another, more interesting example: the shopping cart.

You have a Cart component (parent) that contains several CartItems (children).

But this time, I want the parent to pass information to the child, specifically the post or cart item:

// parent
class Cart extends Component
{
    function addItem()
    {
        $this->dispatch('addItemToCart', $this->post);
    }
}

// child
class CartItem extends Component
{
    ***
    #[On('addItemToCart')]
    function add(Post $post, int $count = 1)
    {***}

Since it's an entity managed by the parent, I need to pass it to the child for it to add or modify. I don't want to complicate things here with technical details, but that's basically it.

Too Many Options?

I'm not criticizing for the sake of criticizing; I love Livewire. But it does have a steeper learning curve compared to Inertia, and it's precisely because of this logic that it can seem somewhat abstract.

Plus, there are multiple ways to do the same thing.

For example:
When working with filters and events, you can define them as:

  • Tags with @,
  • Decorators like @url,
  • Or directly as public properties.

This gives you more options, but also makes programming more complicated.

Best practices you should follow

Performance and lazy loading

  • Use lazy when possible (wire:model.lazy).
  • Divide large components into subcomponents.
  • Avoid unnecessary renders using ->skipRender().
  • Load scripts only when needed.

SEO, accessibility, and user experience

Although Livewire is not a complete SPA framework, it does support:

  • dynamic metas
  • page titles
  • fast navigation
  • accessible components (especially with Flux UI)

You can really use aspects of Livewire along with classic Laravel, and this website is an example of that: I use Laravel Livewire for the Dashboard, and base Laravel for the blog.

Testing, maintenance, and scalability

  • Use tests with Livewire::test().
  • Maintain consistent naming between components and views.
  • Version UI components.
  • Avoid mixing too much logic into a single component (maximum 1 responsibility).

In summary: working with Livewire today feels modern, flexible, and much simpler than before, although unfortunately, pre-defined options previously available with Jetstream, such as avatar upload, session, roles/teams, are no longer included.

FAQs: Frequently Asked Questions about Livewire projects

  • Does Livewire replace Vue or React?
    • It depends on the type of project: for internal panels and CRUDs, yes; for ultra-interactive apps, no.
  • Can I use it along with Alpine.js?
    • Yes, it's a very common combination.
  • Is it suitable for large projects?
    • Yes, especially with well-divided components. The community on Reddit shares good experiences in medium/large projects.
  • What changes in Laravel 12?
    • Volt and Flux become part of the modern Livewire ecosystem, and Jetstream no longer includes as many predefined features.

Now, let's get to know the key piece of Livewire, the components.

We are going to present what we have in detail, the project, its characteristics and how it works.


Ú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:

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español