Next steps: Livewire, Inertia and package development
Choosing between Livewire and an Inertia SPA, Livewire component basics, Inertia page props, and packaging a reusable Laravel package.
Choosing the front-end approach
| Approach | Model | Suits |
|---|---|---|
| Blade only | Server-rendered, full reloads | Content sites, admin forms, small interactivity |
| Livewire | Server-rendered components with AJAX round trips | Forms, tables, dashboards - Laravel developers staying in PHP |
| Inertia + Vue/React | Server routing with client-side rendering | Highly interactive UIs, a team that already knows a JS framework |
| Separate API SPA | Decoupled clients | Multiple clients or third-party consumers |
💡
Livewire and Inertia solve the same problem from opposite ends: Livewire keeps state on the server and sends DOM diffs; Inertia keeps state in the browser and sends page props. Pick the one that matches where your team's strength already is.
A Livewire component
// app/Livewire/SearchBooks.php
class SearchBooks extends Component
{
public string $search = '';
public int $perPage = 10;
protected function queryString(): array
{
return ['search' => ['except' => ''], 'perPage' => ['except' => 10]];
}
public function updating(string $field): void
{
if ($field === 'search') {
$this->resetPage();
}
}
public function render(): View
{
return view('livewire.search-books', [
'books' => Book::query()
->when($this->search, fn ($q) => $q->where('title', 'like', "%{$this->search}%"))
->with('author')
->paginate($this->perPage),
]);
}
}
{{-- resources/views/livewire/search-books.blade.php --}}
<div>
<input type="search" wire:model.live.debounce.300ms="search" placeholder="Search titles">
@foreach ($books as $book)
<div wire:key="book-{{ $book->id }}">{{ $book->title }}</div>
@endforeach
{{ $books->links() }}
</div>wire:keyon items inside a loop, or reordering and deleting produce the wrong DOM updates.wire:model.livesends a request per change;debouncestops it firing on every keystroke.- Every Livewire request re-hydrates the component and re-renders on the server, so keep
render()queries indexed and paginated.
Inertia page props and packaging
class BookController extends Controller
{
public function index(Request $request): Response
{
return Inertia::render('Books/Index', [
'books' => BookResource::collection(
Book::with('author')->withCount('reviews')->paginate(20)
),
'filters' => $request->only('search', 'sort'),
'can' => ['create' => $request->user()->can('create', Book::class)],
]);
}
}
// partial reloads: only the props a component asks for
Inertia::render('Books/Index', ['books' => fn () => Book::paginate(20)]);
// the client: router.reload({ only: ['books'] })# scaffold a distributable package
composer require spatie/laravel-package-tools --dev
mkdir -p packages/laravel-metrics/src
cd packages/laravel-metrics && composer init
# in the package composer.json
# "extra": { "laravel": { "providers": ["Acme\\Metrics\\MetricsServiceProvider"] } }
# then: composer require acme/laravel-metrics- Publish migrations, config and views from the provider so the host application controls them.
- Version with semantic versioning, keep a changelog, and test the package in an isolated Laravel app - the host project can hide missing dependencies.
- Prefer injection or an interface over a facade inside the package, so the host can swap the implementation.
FAQ
Livewire or Vue for a complex admin panel?
Livewire if the interactivity is forms, filters and tables over server data - it removes an entire API layer. Choose Vue or React with Inertia when the UI has rich client state: drag-and-drop canvases, offline editing, or animation-heavy views.
Should I extract a package or keep the code in the app?
Extract when a second application genuinely needs the same code, or when the boundary is stable and testable on its own. Premature extraction adds versioning and release overhead with no benefit.
Related
Deployment and production hardening Controllers, Blade views and Eloquent models
Last refreshed 2026-09-18.