Laravel Laravel Development

    Why Admin Panels Get Slow: Filament Queries, Livewire Re-renders, and When to Reach for Vue

    December 23, 2025
    Updated: September 14, 2026
    Larastaff
    11 min read
    Why Admin Panels Get Slow: Filament Queries, Livewire Re-renders, and When to Reach for Vue

    A Filament panel that loads in 200ms on your machine can take eight seconds for the customer with 400,000 orders. Nothing in your code changed. The table grew.

    Slow panels have three different causes that live in three different layers: the queries Filament builds, the re-render cost Livewire pays on every interaction, and the round trip that no server-rendered framework can avoid. Each has its own fix, and applying the wrong one is how teams spend a sprint and gain nothing.

    This guide shows how to tell the layers apart in two minutes, then fixes each one.

    1. Check Your Versions First, Because Half the Advice Online Is Out of Date

    Most Filament and Livewire performance advice on the web was written for Livewire 2 and Filament 2. Some of it is now wrong rather than merely dated.

    composer show livewire/livewire filament/filament | grep versions

    The current landscape:

    PackageCurrentWhat changed that matters here
    Livewire4.x (Jan 2026)Islands, async actions, deferred loading, parallel live updates
    Filament5.x (Jan 2026)Livewire 4 support. No new Filament features over 4.x, and no breaking changes to your forms, tables, or resources

    Two pieces of old advice to stop repeating:

    • wire:model.defer is Livewire 2 syntax. Since Livewire 3, wire:model is already deferred by default: it syncs to the server when an action runs, not on every keystroke. If you see .defer in your codebase or in an article, that content predates Livewire 3. The modifier you want for search boxes is wire:model.live.debounce.400ms.
    • .blur and .change changed meaning in Livewire 4.1. They used to control only when the network request fired. Now they control client-side sync too. If your code depends on the property updating as the user types, you need wire:model.live.blur.

    Filament 5’s upgrade is unusually cheap: an upgrade script with no manual steps for standard panels. The real prerequisite is Tailwind 4. If you have a custom Filament theme still on Tailwind 3, that’s your actual work.

    2. Find the Cause in Two Minutes, Before Changing Any Code

    Every fix below is wrong for three out of four slow panels. Measure first.

    Add this to AppServiceProvider::boot() in local and staging:

    if (! app()->isProduction()) {
    DB::listen(function (QueryExecuted $query) {
            Log::channel('single')->info('query', [
                'sql' => $query->sql,
                'time' => $query->time,
            ]);
        });
    }

    Then load the slow page once and count:

    grep -c '"sql"' storage/logs/laravel.log

    Read the result against this table:

    What you seeLikely causeSection
    Hundreds of near-identical queriesN+1 in a table column or a badge3
    A handful of queries, one of them slowMissing index, or a COUNT over a huge table4
    Few queries, but every click is slowWhole-component re-render cost5
    Fast first load, slow after every keystrokeA live-bound search without a debounce5

    Filament panels are Livewire components, so Laravel Debugbar won’t capture the follow-up requests. Livewire updates go to /livewire-{hash}/update, a POST request. Open the browser’s network tab, filter on update, and read the timing there. That hash prefix is new in Livewire 4: if your firewall, CDN, or middleware rules still match /livewire/, they stopped matching after the upgrade.

    3. The N+1 That Debugbar Doesn’t Show You

    The classic Filament N+1 isn’t in your query. It’s in a column.

    TextColumn::make('customer.company.name'),
    TextColumn::make('lines_count')->counts('lines'),
    TextColumn::make('status')
        ->badge()
        ->color(fn (Order $record) => $record->latestPayment?->failed ? 'danger' : 'success'),

    With 50 rows per page, that latestPayment call runs 50 extra queries. The first two columns are fine because Filament eager-loads relationship columns and count columns for you. Anything you reach for inside a closure, it can’t see.

    Fix it on the query, not the column:

    public static function table(Table $table): Table
    {
        return $table
            ->modifyQueryUsing(fn (Builder $query) => $query->with([
                'customer.company:id,name',
                'latestPayment:id,order_id,failed',
            ]))
            ->columns([/* ... */]);
    }

    Use modifyQueryUsing() on the table rather than overriding getEloquentQuery() on the resource. getEloquentQuery() applies everywhere the resource is used, including the edit page and relation managers, where that eager load is wasted work.

    To catch the next one automatically, turn lazy loading into an exception in development. Our Laravel performance guide has the AppServiceProvider block for that, and it works the same inside a Filament panel.

    The advice you’ll see elsewhere, and why to skip it: “select only the columns you need” on the whole resource. Narrowing the resource query with select(‘id’, ‘name’, ’email’) breaks edit forms, actions, and anything that reads a column you left out, and the errors show up later, in a different part of the panel. Narrow the eager loads instead, as above, where the scope is obvious.

    4. The Query That’s Slow All by Itself

    Once the query count is small, one query is usually doing the damage. Two common ones:

    The pagination count. Filament runs a COUNT(*) over your filtered query to build the page links. On a few million rows with a filter that can’t use an index, that count can take longer than fetching the page. If your users page through a list rather than jumping to page 47, switch to simple pagination by overriding paginateTableQuery() on the List page, which uses a cursor and never runs the count.

    Searching or sorting a column with no index. Filament’s global search runs LIKE %term% across every column you marked searchable(). A leading wildcard cannot use a B-tree index, so each search is a full scan. Options, in order of effort:

    • Mark fewer columns searchable. Most panels have one or two columns anyone actually searches by.
    • Use searchable(isIndividual: true) so the search applies per column instead of across all of them.
    • For genuinely large tables, wire in Laravel Scout by overriding applySearchToTableQuery() and filtering with whereIn(‘id’, Model::search($term)->keys()).

    Before adding an index, confirm it’s the problem:

    EXPLAIN SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC LIMIT 50;

    If type is ALL and rows is in the hundreds of thousands, you’re scanning the table. If it already says ref or range, the index isn’t your bottleneck and adding another one won’t help.

    For a table that’s slow no matter what you do, deferLoading() renders the page first and fetches rows in a second request. It doesn’t make anything faster. It makes the panel feel usable while you fix the real cause, which makes it a reasonable stopgap and a bad permanent answer.

    5. Re-render Cost, and What Livewire 4 Changed

    If your query count is low but every interaction still takes 400ms, the cost is rendering. A Livewire component re-renders its whole template on every request, and a dashboard with six widgets re-renders all six when you click one.

    Livewire 4 fixed this directly with islands: isolated regions inside one component that update on their own.

    <div>
       @island(name: 'revenue', lazy: true)
            @placeholder
                <div class="h-32 animate-pulse rounded bg-gray-200"></div>
            @endplaceholder
    
            <div>
                Revenue: {{ $this->revenue }}
                <button type="button" wire:click="$refresh">Refresh</button>
            </div>
        @endisland
    
        {{-- The rest of the dashboard doesn't re-render when revenue refreshes --}}
    </div>

    Two constraints worth knowing before you plan around islands. Islands can’t be used inside @foreach or @if, because they don’t have access to loop or conditional variables. Put the loop inside the island instead. And if an island request and a root component request are in flight at once and both change the same property, the last response wins. For independent widgets that’s fine. For a shared filter, it isn’t.

    Three more things that cut re-render cost, in rough order of payoff:

    Move queries out of render() and into computed properties. A #[Computed] method is memoized for the request and only runs when the template actually reads it. Inside a lazy island, it doesn’t run at all until the island loads.

    Mark tracking actions renderless. An action that writes an audit row doesn’t need to re-render anything:

    <button wire:click.renderless="trackExport">Export</button>

    Use .async for slow, independent actions. In Livewire 4, wire:click.async=”logActivity” runs in parallel instead of blocking the next request. Filament 5 ships async requests by default, which is why opening a relationship select no longer blocks the rest of the panel.

    Always add wire:key in loops. Livewire 4 turns on smart_wire_keys by default, which helps with nested components, but it does not remove the need for a key inside a loop. Missing keys show up as the wrong row updating after a delete, which looks like a data bug and isn’t.

    6. The Third Layer: When the Round Trip Is the Problem

    Sections 3 to 5 assume the fix is on the server. Sometimes it isn’t, and no amount of eager loading or island work will help.

    The common rule, “Livewire for admin, Vue for public”, gets the right answer for the wrong reason, which means it gets the wrong answer at the edges.

    The real question is: does this interaction need to respond faster than a network round trip?

    Livewire runs your interaction logic on the server. A click means a POST, a database read, a render, and a response. On a corporate LAN that’s 30ms and feels instant. For a user in Sydney hitting a server in Virginia, the same interaction is 300ms before your code does anything.

    Use that to decide:

    InteractionRound trip acceptable?Use
    Admin CRUD, filters, bulk actionsYesLivewire or Filament
    Multi-step form with server validationYesLivewire
    Drag-and-drop reorderingBorderlineLivewire’s wire:sort handles the common case
    Canvas, map, or chart with live panningNoVue or Alpine
    Offline or intermittent connectivityNoVue
    Rich text or code editorNoA JS library, wrapped in wire:ignore
    Real-time dashboard on a global user baseDepends on latencyMeasure before deciding

    Three practical notes.

    You don’t have to pick one for the whole app. wire:ignore lets you mount a Vue component inside a Livewire page for the one widget that needs local state, and Livewire won’t touch its DOM on re-render. Alpine covers most smaller interactions with no build step at all.

    Mounting Vue inside Livewire has one rule. Everything inside wire:ignore is invisible to Livewire, so state has to cross the boundary explicitly: emit a Livewire event from Vue to send data back, and pass props in on mount. Teams that skip this end up with two copies of the same state that drift apart.

    An admin panel used by a distributed team is still latency-sensitive, which is exactly the case the “admin means Livewire” rule gets wrong. Measure the round trip from where your users actually are before deciding the framework is the problem.

    7. Signs the Panel Needs Work Beyond These Fixes

    • The same table is slow after you’ve cut it to fewer than ten queries.
    • Bulk actions time out, or hit PHP’s memory limit, on a real customer’s data.
    • A single resource file is over a thousand lines, so nobody can tell what a change affects.
    • You’re still on Filament 3 or Livewire 2, and upgrading keeps getting deferred.
    • Exports run in the request instead of on a queue, so the browser waits.

    Each of these is a different fix. Measuring, as in section 2, is what tells you which.

    FAQ

    Should I upgrade to Filament 5? It has no new features over Filament 4, so there’s no feature pressure. Upgrade for what Livewire 4 gives you: islands, async requests, and deferred loading. The script handles the mechanical work. Budget the time for the Tailwind 4 requirement if you have a custom theme.

    Does Filament work outside an admin panel? Yes. The table and schema builders are standalone packages you can use in any Livewire component. Everything in sections 3 and 4 applies there too.

    We already use Vue for our admin. Should we move to Filament? Only if the panel is mostly CRUD. The gain is the forms, tables, filters, and actions you stop writing, not raw speed. If your admin has heavy client-side interaction, you would be trading a real problem for the round trip in section 6.

    Is wire:model.defer wrong now? It’s ignored as an unknown modifier rather than throwing an error, so a form using it still works, but only because plain wire:model is already deferred in Livewire 3 and 4.

    Send Us One Slow Panel

    Tell us which Filament page is slow, roughly how many rows the table holds, and what “slow” means for you, for example “the orders list takes 9 seconds.” We’ll reply with the most likely cause and how to confirm it, within 3 business days. No call required. Contact us.

    Tags

    Filament Livewire Vue.js
    Larastaff

    Larastaff

    Related Articles

    Leave a Comment

    Your email address will not be published. Required fields are marked *