Our services

If you can think it, we can make it brainsoft.

Where Laravel apps slow down, and how to find it

Written By: BrainSoft In Backend

A Laravel app rarely falls over in one dramatic moment. It gets slower in small places nobody looked at: a relationship loaded inside a loop, a cache that never hits, a queue worker doing synchronous work, an index that was never added. The page still renders, so nobody files a ticket until response times creep past what users tolerate.

This walks through the usual suspects in the order I check them, plus the tools that turn a vague feeling of slowness into a specific line of code. Start with measurement, fix the biggest offender, then measure again.

Measure before you touch anything

The instinct is to open a controller and start optimizing. Don't. You need one number you can compare against, and it should come from the environment that is actually slow. Local machines with seeded databases lie to you.

Laravel Telescope is the fastest way to see what happened during a single request. Install it in a staging environment, open the slow page, and look at the request detail. Telescope shows every query, every job dispatched, every cache call, and how long each took. If you only do one thing from this article, do this.

composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

For production, where Telescope is too heavy, use Laravel Pulse or a proper APM. Pulse gives you slow queries, slow jobs, and slow requests with sampled data, which is enough to spot a pattern without instrumenting everything.

The four things that usually cause it

Across the Laravel codebases we audit, the same handful of problems show up again and again. None of them are exotic.

  • N+1 queries. You fetch 50 orders, then access $order->customer->name inside a Blade loop. That is 51 queries. Telescope makes this obvious because you see the same query shape repeated with different bindings.
  • Missing indexes. A where or orderBy on a column with no index forces a full table scan. It is fine with 2,000 rows and painful with 2 million.
  • Cache that never hits. Someone added Cache::remember with a key that includes a timestamp, so it misses every single call. The code looks optimized and does nothing.
  • Queue work done inline. Sending mail, calling an external API, or generating a PDF inside a web request. Anything over a few hundred milliseconds belongs in a job.

Fixing N+1s is usually a one-line change with eager loading:

// 51 queries
$orders = Order::latest()->take(50)->get();

// 2 queries
$orders = Order::with('customer')->latest()->take(50)->get();

You can catch these before they reach production by enabling strict mode in a local or CI environment, which throws when a relationship is lazy loaded:

// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());

Reading the query log properly

Telescope is great for one request. When you need to compare across many, log slow queries at the database level. MySQL's slow query log plus EXPLAIN tells you whether an index is being used or ignored.

EXPLAIN SELECT * FROM orders
WHERE status = 'paid' AND created_at > '2024-01-01';

If the type column says ALL, you are scanning the whole table. A composite index on (status, created_at) usually fixes it. Watch out for whereDate() and similar helpers that wrap the column in a function and quietly disable the index.

On the PHP side, check where time actually goes. Xdebug or Blackfire will show you a flame graph, and the answer is often not the database at all. It is a serialization step, a regex, or a loop building a large array in memory. If you would rather have someone do this audit with you, get in touch and we can look at your slowest endpoints together.

What to fix first

Work on the slowest endpoint that the most users hit. A 4-second admin report nobody opens is not worth your afternoon. A 900ms product listing on every page view is.

After each change, re-measure with the same tool and the same data. Keep a short note of before and after. That habit turns performance work from guesswork into something you can defend in a code review, and it stops the next person from undoing your index because they did not know why it was there.

Frequently asked questions

Is Laravel itself slow?

No. Framework overhead is small compared to database access and external calls. Most slow Laravel apps are slow because of query patterns and blocking I/O, not because of the framework. Caching the config and routes in production helps a little, but it will not fix an N+1.

Should I use Telescope in production?

Generally no. It stores a lot of data and adds overhead to every request. Use it in staging for detailed debugging, and use Pulse or an APM tool in production where you need sampled, low-cost visibility.

How do I know if a query needs an index?

Run EXPLAIN on it. If the type is ALL or the rows estimate is close to the table size, add an index on the columns used in the where and order by clauses. Composite indexes should match the order the columns appear in your query.


#Backend