Livewire Pagination: The Empty-Page Bug and How to Actually Fix It
Livewire's WithPagination trait makes paginated tables trivial — until search plus page 4 returns an empty table. Here's why that happens, where resetPage() belongs, and the trade-offs of server-driven paging.
17 Dec 2025, 03:59 UTC

You wire up a Livewire table with pagination and a search box, click to page 4, then type a query that matches two records — and the table shows nothing. No error, no empty-state message you wrote, just blank rows. This is the most common pagination bug in Livewire, and it has a one-line fix. The catch is knowing where that line goes.
This post assumes Livewire v3 on Laravel 10/11. In v2, the trait and query-string APIs differ, so check your installed version with composer show livewire/livewire before copying anything below.
How WithPagination actually works
Livewire's WithPagination trait turns a component into a paginated view with no custom endpoints. You call paginate() inside render(), and the trait wires up the page state, the navigation links, and the re-render on page change. Every click on a page link is a network round-trip to the server, which re-runs the query and returns fresh HTML.
By default, the current page syncs to the ?page= query string. That means a user can refresh the browser or paste a URL into a chat and land on the same page of results — genuinely useful, and free.
A minimal working component
Here's a user list with search. Run php artisan make:livewire UserTable (needs write access to your Laravel app directory), then fill it in:
use Livewire\Component;
use Livewire\WithPagination;
use App\Models\User;
class UserTable extends Component
{
use WithPagination;
public string $search = '';
public function updatingSearch(): void
{
$this->resetPage();
}
public function render()
{
return view('livewire.user-table', [
'users' => User::where('name', 'like', "%{$this->search}%")
->paginate(10),
]);
}
}In the Blade view, render the rows and then {{ $users->links() }}. That's the whole feature. The updatingSearch() hook is the important part — more on it next.
The empty-page bug, explained
The page number is component state. When the search narrows 500 users down to 2 but the component still thinks it's on page 4, Laravel's paginator happily runs OFFSET 30 LIMIT 10 against a 2-row result set and returns nothing. From the user's perspective, search is broken.
The fix is $this->resetPage(), but placement matters. It must run in an updating* lifecycle hook (which fires before the property changes and the component re-renders), not in updated* or inside render(). If you reset the page during render, users can never leave page 1.
To reproduce and verify the fix: seed about 50 users, navigate to page 3, then search for a single name. Without the hook you get an empty table; with it, you land on page 1 showing the match. You can also assert this in a feature test: Livewire::test(UserTable::class)->set('page', 3)->set('search', 'alice')->assertSee('[contact removed]').
Multiple paginated components on one page
Drop two paginated components on the same page — say, "Open tickets" and "Closed tickets" — and their ?page= parameters collide, so clicking a page link in one table jumps the other. Give each a distinct page name:
// inside the second component's render()
Ticket::closed()->paginate(10, ['*'], 'closedPage')The third argument names the query-string parameter, so the URL carries ?page=2&closedPage=1 and each component tracks its own state.
Trade-offs worth knowing before you ship
Every page change is a server round-trip. That's the design: results are always fresh and server-authoritative, but it's not the instant feel of a client-side cache. On slow connections, add wire:loading indicators so the table doesn't look frozen.
The COUNT query costs you. paginate() runs a COUNT(*) to build the numbered page links. On large tables that's expensive. simplePaginate() skips the count and renders only previous/next links — often the right call for feeds or admin tables where "page 47 of 213" adds nothing.
Deep OFFSET gets slow. Page 10,000 means the database scans and discards 100,000 rows. If you genuinely expect users (or API-style consumers) to page deep into millions of rows, look at cursor pagination instead — but for typical admin UIs, offset pagination with an index on your sort column is fine.
What to do next
Audit any Livewire component that combines pagination with filters, search, or sort: every filter-changing property needs an updating* hook calling resetPage(). Then write one feature test per component asserting that setting a page and then a filter still shows expected records. That test is cheap, and it catches the regression the day someone adds a second filter and forgets the hook.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.