Skip to content

Livewire vs Inertia vs React: How I Pick a Laravel Frontend

Livewire vs Inertia vs React: How I Pick a Laravel Frontend

I’ve built more or less the same orders screen three times in the past two years. Once with Livewire, once with Inertia.js and React, and once as a standalone React SPA talking to a Laravel API. Same feature, similar clients, wildly different week-to-week experience. The Livewire version shipped fastest. The Inertia version is the one I still enjoy maintaining. The SPA is the one that taught me to stop defaulting to SPAs.

So if you’re staring at a fresh Laravel app wondering which frontend to commit to, here’s the short version for the impatient: pick Livewire if your app is mostly forms, tables, and dashboards. Pick Inertia.js if you want real React without running a second project. Build a separate SPA only if something other than your own web frontend needs that API. The rest of this post is me defending that answer, with code.

Livewire: the page is the app

Laravel Livewire’s pitch is that you write your components in PHP and the framework handles the reactivity over the wire. No API layer, no client-side router, no second mental model living in a resources/js folder.

Here’s what it replaces. The old way, when I wanted live search on an orders table, meant a dedicated JSON endpoint plus a pile of JavaScript:

// routes/web.php, the pre-Livewire way
Route::get('/orders/search', [OrderController::class, 'search']);
// ...plus ~40 lines of fetch(), debounce logic, and
// innerHTML surgery in a Blade-adjacent JS file

With Livewire it’s one class and one template:

class OrderSearch extends Component
{
    public string $query = '';

    public function render()
    {
        return view('livewire.order-search', [
            'orders' => Order::where('number', 'like', "%{$this->query}%")
                ->limit(10)
                ->get(),
        ]);
    }
}
<input type="text"
       wire:model.live.debounce.300ms="query"
       placeholder="Search orders...">

That one wire:model attribute does the job of my old fetch-and-debounce file. For CRUD-heavy internal tools, this is hard to beat on speed of delivery. Filament, the admin panel framework I wrote about in the post where I stopped rolling my own admin panels, is built entirely on Livewire, and it’s the strongest argument for the stack: a whole ecosystem of ready-made components that all speak PHP.

Where it bites: every interaction is a network round trip. Debounced search feels fine. A drag-and-drop board doesn’t. And when a design calls for serious client-side state, think optimistic updates, animation tied to data, or offline tolerance, you end up writing Alpine.js islands inside your Livewire components and quietly wishing you had React.

Inertia.js: React without building an API

Inertia.js calls itself “the modern monolith,” and for once the tagline is accurate. Your routing, controllers, validation, and auth stay plain Laravel. The difference is that controllers return React (or Vue, or Svelte) page components instead of Blade views:

// app/Http/Controllers/OrderController.php
public function index(Request $request)
{
    return Inertia::render('Orders/Index', [
        'orders'  => Order::search($request->q)->paginate(20),
        'filters' => $request->only('q'),
    ]);
}
// resources/js/Pages/Orders/Index.jsx
import { router } from '@inertiajs/react'

export default function Index({ orders, filters }) {
  const search = (q) =>
    router.get('/orders', { q }, { preserveState: true, replace: true })

  return (
    <>
      <input
        defaultValue={filters.q}
        onChange={(e) => search(e.target.value)}
        placeholder="Search orders..."
      />
      <OrderTable orders={orders.data} />
    </>
  )
}

Look at what’s missing. There’s no API route, no axios layer, no token handling, no duplicated validation, no OpenAPI doc that nobody updates. Props flow from the controller into the component the way data used to flow into Blade. Laravel’s own React starter kit ships with Inertia wired in, which tells you where the framework’s heart is these days.

The costs are real but smaller than people fear. You own a Vite build and a package.json now. Server-side rendering for SEO means running a small Node process next to PHP. And Inertia’s everything-is-a-page-visit model takes a week to internalize if you’re used to SPAs; preserveState and partial reloads exist precisely because the naive version re-renders more than you’d like.

I went into this stack skeptical, mostly because I’d been burned by JS churn before. Eight months in, the Inertia app is the codebase I complain about least. I also got the livewire vs inertia call wrong on one project, an analytics-heavy dashboard I started in Livewire, and the rewrite cost me a month. That month is why this post exists.

When a separate React SPA earns its keep

There’s still a case for the classic setup: React in one repo, Laravel as a pure API in another. I reach for it when the API has more than one consumer. A React Native app. A partner integration. A public API that’s the actual product. In those cases the API isn’t overhead, it’s the deliverable.

But if the only consumer of your API is your own web frontend, you’re designing, versioning, documenting, and securing an API for an audience of one. I’ve done it. It’s the most expensive way I know to render a form. You also inherit the full client-side state problem, which usually means TanStack Query or Zustand doing cache work that Inertia would have handled invisibly. I compared those options in my Zustand vs Redux post if you’re already down that road.

The “laravel vs react” framing you see in forum threads is mostly a false choice, by the way. Laravel is the backend either way. The real question is how much JavaScript you put between your controllers and the browser, and who has to maintain it.

Performance and SEO, since you’ll ask

Two things come up in every consultation, so let’s get them out of the way.

SEO first. Livewire wins by default. The server renders HTML, crawlers read HTML, done. Inertia.js renders client-side out of the box, so a crawler that doesn’t execute JavaScript sees an empty div. Inertia’s SSR mode fixes this, but it means a Node process running alongside PHP that you have to deploy, supervise, and restart when it leaks memory. I run one in production. It’s fine. It’s also one more thing that pages me. If the app sits behind a login, none of this matters and you should ignore everyone arguing about it.

Latency is the sneakier one. Livewire’s interactions are network round trips, so the experience degrades with distance from your server. My server lives in Germany. For a client whose users were mostly in Southeast Asia, a Livewire modal that felt instant to me took a noticeable beat for them, on every single click. Inertia keeps interactions like sorting a table or toggling a panel in the browser, so the ocean between user and server only matters on actual page loads.

Neither of these is a dealbreaker on its own. But if you’re public-facing and far from your users, the defaults quietly favor Inertia. If you’re an internal tool on the same continent as your team, they favor Livewire. Geography is an underrated input into what looks like a pure technology decision.

The questions I actually ask

Forget feature matrices. Four questions settle this for me on client projects, and most of the work in my portfolio is some flavor of Laravel plus React, so I’ve had plenty of chances to test them.

Does anything besides the web frontend consume this data? If yes, build the API and the SPA. If no, keep the monolith.

How interactive is the most complicated screen? Forms and tables: Livewire. Heavy client state, optimistic UI, drag-and-drop: Inertia with React. Be honest about the worst screen, not the average one, because the worst screen is where you’ll spend your time.

Who maintains this in a year? A PHP team that tolerates JavaScript ships faster with Laravel Livewire. A team that already thinks in React components will resent Blade. I’ve watched both resentments play out, and they cost more than any benchmark difference.

Does the budget survive a wrong guess? Mine didn’t, once. Moving from Blade to Inertia is mostly additive. Moving a half-grown Livewire app to React means rewriting components in another language, and going the other direction is just as painful.

Try both this week

Don’t take my word for any of this. The Laravel installer scaffolds both stacks in about two minutes each:

laravel new try-livewire   # pick the Livewire starter kit
laravel new try-inertia    # pick the React starter kit

Build the same small thing in both. A settings form with validation and a flash message is perfect. Notice which one you fight and which one disappears. The stack that disappears is the right one for that project. For me that’s been Inertia.js more often than not, but I’d rather you find your own answer in an afternoon than inherit my bias for a year.