Skip to main content
Available in:
EN FR

Livewire vs Alpine.js en 2026 : la ligne n'a pas bougé (voici comment la tracer)

5 août 2026 Temps de lecture : 4 min TALL happytodev happytodev
Livewire vs Alpine.js en 2026 : la ligne n'a pas bougé (voici comment la tracer)

TL;DR

En bref : Alpine pour tout ce qui vit entièrement dans le navigateur — menus, toggles, onglets. Livewire pour tout ce qui a besoin du serveur — recherche, formulaires, état persistant. Ils s'imbriquent : Livewire détient les données, Alpine gère l'UX autour. En 2026, la ligne n'a toujours pas bougé.

Quand tu développes une fonctionnalité — un dropdown, une recherche, un formulaire — la première question est souvent : Alpine ou Livewire ?

Voici le modèle mental que j'utilise : Alpine gère tout ce qui naît et meurt dans le navigateur. Livewire gère tout ce qui doit parler au serveur. Le reste n'est qu'une question de savoir de quel côté de la ligne se trouve ton interaction.

La confusion est réelle parce que les deux font partie de la stack TALL et que les deux fonctionnent avec des attributs HTML déclaratifs. Mais ils résolvent des problèmes différents. Alpine, c'est du JavaScript que tu n'as pas à écrire — il tourne entièrement dans le navigateur, sans aucune requête réseau. Livewire, c'est de l'état côté serveur avec du DOM diffing — ton composant PHP détient la vérité, et le navigateur ne fait que rendre ce que le serveur a décidé. Un dropdown Alpine s'ouvre et se ferme en mémoire. Une recherche Livewire interroge la base de données et revient avec le HTML des seules parties modifiées de la page. Même apparence pour l'utilisateur, opérations radicalement différentes sous le capot.

Quand je me tourne vers Alpine, le serveur n'a pas besoin de prendre de décision. Un menu mobile, un switch de thème, des onglets — tout ce qui a déjà sa réponse côté client. Ce dropdown qui s'ouvre et se ferme ? Alpine. Zéro requête, instantané, aucun aller-retour.

<div x-data="{ open: false, dark: false }">
  <button @click="open = !open">Menu</button>
  <div x-show="open" x-transition>
    <button @click="dark = !dark">Toggle dark mode</button>
  </div>
</div>

Quand je me tourne vers Livewire, le serveur est la source de vérité. Une recherche qui interroge la base de données. Un formulaire qui valide et enregistre. Un compteur qui persiste entre les sessions. Tout le panneau d'administration de Filament est du Livewire — parce que la base de données est la source de vérité. Voici une recherche complète, pas un bout de code :

<input
  type="search"
  wire:model.live="search"
  placeholder="Search users..."
>

<div wire:loading>
  Searching...
</div>

<ul>
  @foreach ($users as $user)
    <li>{{ $user->name }}</li>
  @endforeach
</ul>
use Livewire\WithPagination;

class SearchUsers extends Component
{
    use WithPagination;

    public string $search = '';

    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function render()
    {
        return view('livewire.search-users', [
            'users' => User::query()
                ->where('name', 'like', "%{$this->search}%")
                ->paginate(10),
        ]);
    }
}

Chaque frappe de clavier déclenche une requête, la requête SQL s'exécute, et seules les parties modifiées du DOM sont remplacées. C'est ça, l'aller-retour que tu achètes.

Le piège à éviter, c'est d'utiliser Livewire là où Alpine suffirait. Mettre un wire:poll sur une animation d'ouverture/fermeture, c'est taper le serveur pour quelque chose que le navigateur sait faire tout seul — une manière coûteuse d'éviter dix lignes de JavaScript. Et l'inverse est tout aussi mauvais : pousser de l'état serveur dans Alpine (un dropdown qui lit une variable PHP qui ne change jamais après le chargement), c'est de l'état dupliqué qui te mordra dès que la source de vérité bougera.

Là où ça devient intéressant, c'est quand on les imbrique. Reprends la recherche ci-dessus et fais du dropdown lui-même du Alpine — Livewire récupère les données, Alpine gère la navigation clavier et le ressenti d'ouverture/fermeture. Ils cessent d'être concurrents et deviennent deux couches du même composant.

<div
  x-data="{ open: false, highlighted: 0 }"
  @keydown.down.prevent="highlighted = Math.min(highlighted + 1, {{ $users->count() - 1 }})"
  @keydown.up.prevent="highlighted = Math.max(highlighted - 1, 0)"
>
  <input
    type="search"
    wire:model.live="search"
    @focus="open = true"
    @blur="setTimeout(() => open = false, 150)"
  >

  <ul x-show="open" x-transition>
    @foreach ($users as $i => $user)
      <li
        @mouseenter="highlighted = {{ $i }}"
        @click="$wire.select({{ $user->id }})"
        :class="highlighted === {{ $i }} ? 'bg-gray-100' : ''"
      >{{ $user->name }}</li>
    @endforeach
  </ul>
</div>

Livewire embarque Alpine et expose $wire à l'intérieur, donc appeler le composant depuis le client est un geste de première classe : $wire.select(id), $wire.set('search', value), $wire.users. Tu n'assembles pas deux bibliothèques au forceps — l'imbrication est le comportement par défaut, pas une exception à justifier. Une note pour la production : injecter des variables Blade directement dans x-data comme je viens de le faire fonctionne très bien, mais le DOM est re-généré à chaque render Livewire. Si l'état Alpine doit survivre à ça, garde-le du côté Alpine et laisse Livewire ne fournir que les données — c'est le découpage le plus propre sur un vrai composant.

La ligne se brouille à un endroit qui mérite d'être signalé : l'UI optimiste. Si tu veux qu'une case à cocher se coche instantanément et ne se synchronise avec le serveur qu'en arrière-plan, c'est une interaction Alpine qui parie que Livewire suivra. Si la valeur doit être validée et stockée avant que l'UI puisse lui faire confiance, c'est du ressort de Livewire. La validation est le cas le plus clair — des contrôles temps réel côté client avec Alpine pour le ressenti, une validation serveur faisant autorité avec Livewire à l'envoi. Même interaction, deux couches, chacune là où elle excelle.

Est-ce que quelque chose a changé en 2026 ? Pas fondamentalement. Livewire v3 et v4 et Alpine v3 tracent toujours la même ligne, et la philosophie des docs Livewire — Alpine pour le côté client, Livewire pour tout ce qui touche au serveur — tient toujours. Ce qui s'est amélioré, c'est la friction entre les deux : avec $wire et l'Alpine embarqué, les coutures sont presque invisibles.

L'heuristique 80 % que j'utilise : si l'interaction doit parler au serveur pour être correcte, c'est Livewire. Si elle peut se résoudre entièrement dans le navigateur, c'est Alpine.

**Où traces-tu la ligne dans tes propres projets — et quelle est la chose que tu as construite qui t'a fait remettre ta propre règle en question ? **

HappyToDev

À propos de l'auteur

HappyToDev

Hello moi c'est Fred, mais vous me connaissez plutôt via mon pseudo : HappyToDev.

Ma bio

Mari et 2 fois papa 💪

Ex de la Marine Nationale 🫡

Passionné d'informatique depuis mes 10 ans, j'ai le code dans la peau. Je suis toujours partant pour un laravel new 😉

Créateur des newsletters 🗞️ :

Créateur de Framework Heroes 🦸🏽‍♀️🦸🏻‍♂️

J'avais envie de faire ce site depuis plusieurs mois pour donner naissance à un job board spécialisé sur les devs utilisant des frameworks. J'espère que le concept vous plaira et que vous m'aiderez à le développer en m'apportant de nouvelles idées.

Précision utile : pour les devs c'est gratuit et vous pouvez y créer votre page profile et la partager avec une url unique. Voici la mienne en exemple

Jeter un oeil à Framework Heroes

Créateur de GiftKeepr 🎁

Une question simple : recevez vous des cadeaux qui ne vous plaisent pas à votre anniversaire, à Noël ou lors d'autres occasions ?

GiftKeepr est là pour que cela n'arrive plus jamais. Et c'est super simple !

  • Créez votre profil
  • Renseignez les cadeaux que vous souhaitez
  • Communiquez votre adresse GiftKeepr à votre entourage
  • Recevez les bons cadeaux pour les occasions que vous avez défini
  • Easy peasy !!

Jeter un oeil à GiftKeepr

Commentaires ()

Répondre
/