Laravel 12 starter kits: Livewire or React with Inertia?

Choose between Livewire and React with Inertia under explicit team constraints, then inspect a real account response, shared props and React page resolution.
Compare two ownership models, not three products
Laravel’s React starter kit uses Inertia. React supplies the components; Inertia connects them to Laravel’s routes and responses. Livewire is a different frontend approach. Treating React and Inertia as separate competing kits gives a team the wrong choice to evaluate.
The Laravel 12 documentation describes its React kit with React 19 and Inertia 2. Inertia’s documentation now identifies v3 as the default, so this article keeps the v2 generation explicit. The worked app pins Laravel 12.69.3, inertia-laravel 2.0.28, the React adapter 2.3.29 and React/React DOM 19.2.9. It is a minimal fixture, not the output of today’s installer.
I recommend choosing who will maintain interaction behavior before choosing a kit. A small controller response is useful, but it does not describe the whole browser payload: Inertia’s shared data is merged with page props. The example below inspects that combined boundary instead of assuming an explicit account array excludes unrelated fields everywhere.
Make a decision under concrete constraints
Consider a fictional team maintaining an existing Laravel application. The next screen displays account details and offers an ordinary server-validated edit form. Its maintainers work mainly in PHP and Blade, and no required interaction depends on a substantial React component library. Those are stipulated constraints, not findings from a client project.
I recommend Livewire for that brief, subject to a prototype of its actual form and failure behavior. I would reject introducing React solely to display account fields: this brief has not supplied a browser interaction that justifies the additional ownership. Laravel describes Livewire as a fit for teams working primarily with PHP and Blade. This is a conditional maintenance decision, not a measured speed or cost advantage.
Change the brief: the team already maintains React components, and the screen must support a browser-local editor before submitting an explicit change. I recommend evaluating React with Inertia first in that case. The relevant tradeoff is ownership of local state and its reconciliation with server responses. Neither option removes the need for server permissions, and choosing React does not by itself require a separately deployed API.
The following read-only account fixture tests one Inertia boundary. It does not compare both implementations of the fictional edit form, test save conflicts or establish a universal winner. Keep those missing prototype questions on the decision record rather than inventing round-trip counts.
Review controller props and global shared data together
The account route in the bundle uses Laravel’s auth middleware, then the controller authorizes view-account before constructing its response. It sends displayName in account. Separately, HandleInertiaRequests merges the adapter’s parent shared data with an auth namespace containing only the actor ID. An application that shares a whole user elsewhere would need to inspect that data too.
In the normal initial response, the complete props object contains errors, auth and account. Its errors object is empty. The auth object contains id: 1, and account contains displayName: “Fixture Owner”. The synthetic user also has an email and a private-field specimen, neither of which appears in that page response. The later Inertia JSON response is checked against the same complete props. These observations cover this fixture, not every page of a generated kit.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Inertia\Inertia;
use Inertia\Response;
class AccountController
{
public function __invoke(Request $request): Response
{
Gate::authorize('view-account');
return Inertia::render('account/show', [
'account' => ['displayName' => $request->user()->name],
]);
}
}
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
protected $rootView = 'app';
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'auth' => ['id' => $request->user()?->getAuthIdentifier()],
]);
}
}
Connect the page name to a real React component
The controller’s account/show name resolves to the imported Account component. The root Blade view supplies the initial Inertia page and compiled app bundle. The fixture uses esbuild for that bundle, with Node 22.20.0 and locked dependencies inside its build image. It does not test Vite configuration or server-side rendering.
The page displays the server’s name and keeps an editable preview in React state. Changing that preview updates the displayed status without an observed HTTP request during that interaction. It does not save anything. A server-controlled permission or saved value must not be inferred from that local preview.
The bootstrap also records a React version and captures resolver failure for the test harness. Those window flags are observation controls, not a recommended application interface. The browser test follows Away through the Inertia protocol and returns with Back while retaining a test marker on the same document. It does not claim that arbitrary local state or private history data is restored safely.
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createInertiaApp, Link } from '@inertiajs/react';
import Account from './pages/account/show.jsx';
function Away() {
return <main><h1>Away</h1><Link href="/account">Account</Link></main>;
}
const pages = { 'account/show': Account, away: Away };
window.fixtureReactVersion = React.version;
createInertiaApp({
resolve(name) {
if (!pages[name]) throw new Error(`Missing fixture page: ${name}`);
return pages[name];
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />);
},
}).catch(error => { window.fixtureFailure = String(error); });
import React, { useState } from 'react';
import { Link } from '@inertiajs/react';
export default function Account({ account }) {
const [preview, setPreview] = useState(account.displayName);
return <main>
<h1>Account</h1>
<p data-display-name>{account.displayName}</p>
<label>Local display preview
<input value={preview} onChange={event => setPreview(event.target.value)} />
</label>
<p role="status">Preview only: {preview}</p>
<Link href="/away">Away</Link>
</main>;
}
Test failures that a small props array cannot prevent
The recorded normal run passed 29 HTTP/browser observations. They include initial page data, the complete Inertia JSON response, the resolved and rendered component, local preview behavior and navigation. A denied synthetic actor receives 403. A guest receives 401 for a JSON request and a redirect to the synthetic login destination for an HTML request. No real login flow was implemented.
Six actual source mutations test distinct boundaries. Sharing the full user fails shared_props_minimal. Returning the full user as account fails account_props_minimal. Removing the policy check fails denied_request_status. Changing the server page name fails initial_component_name, deleting its resolver entry fails page_resolver_succeeded, and rendering the wrong field fails display_name_rendered.
Each control must fail at its intended assertion with the exact preceding normal observations and hashes of the source files inside the running image. Laravel runs unprivileged with no external network or published port. A same-origin loopback relay carries real HTTP through its middleware. It does not replace the controller with a mocked response.
Keep the decision and the evidence at the same scope
I recommend using this fixture as a model for inspecting the complete response boundary, then prototyping the interaction that actually separates the candidates. For the PHP-oriented fictional brief, that still means checking a real Livewire form before committing. For the React-oriented brief, test the editor’s server reconciliation and failures rather than accepting a read-only account screen as sufficient evidence.
The bundle pins dependencies and includes reproduction commands. Synthetic guard users do not establish authentication or tenant isolation. The tests do not cover writes, CSRF, session-expiry recovery, production TLS, accessibility across assistive technologies or performance. A passing Inertia account page supplies evidence about that page. It cannot settle a different workflow or a whole starter-kit choice.
Reproduce the examples
Download the exact Laravel/Inertia app, React page/resolver, dependency locks and normal/mutation observations. Run npm ci and node verify.mjs results.json from the fixture directory with Docker and pinned Playwright available. Synthetic read-only account boundary; no real login or comparative benchmark.
Download the source and tests (.zip)
