Journal/ Laravel/ Performance/ PHP
Laravel performance optimization: a practical checklist
A practical Laravel performance checklist — N+1 queries, indexes, caching, queues, OPcache, config caching and front-end weight — in the order I actually check them.
Senior Full-Stack Developer, Nepal
- Published
- Reading time
- 3 min read
When a Laravel application feels slow, the temptation is to reach for a bigger server or a clever cache. In my experience, most slowness comes from a handful of predictable causes. Fixing them in the right order gets you most of the gain for very little effort.
This is the order I work through.
1. Measure before changing anything
Guessing wastes time. Find out where the time goes first:
- Laravel Debugbar or Telescope in local and staging environments show every query, its duration and where it was triggered.
DB::listen()can log slow queries in production:
DB::listen(function ($query) {
if ($query->time > 200) { // milliseconds
Log::warning('Slow query', ['sql' => $query->sql, 'time' => $query->time]);
}
});
- Your database’s slow query log (MySQL
slow_query_log, PostgreSQLlog_min_duration_statement) catches everything else.
Nine times out of ten, the problem is in the database.
2. Kill N+1 queries
The classic Laravel performance bug: a list of 50 orders that runs 51 queries because each row loads its customer lazily.
// 1 query for orders + 1 per order for the customer
$orders = Order::latest()->take(50)->get();
// 2 queries in total
$orders = Order::with('customer')->latest()->take(50)->get();
Make lazy loading fail loudly during development so these never reach production:
// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());
Use withCount() and withSum() instead of loading whole relationships just to count or total them.
3. Add the right indexes
An unindexed WHERE or ORDER BY on a large table forces a full scan. Look at the slow queries from step 1 and add indexes that match them — often composite ones:
$table->index(['user_id', 'created_at']); // a customer's order history
$table->index(['status', 'created_at']); // admin dashboard filters
Run EXPLAIN before and after. Don’t index everything: each index slows down writes.
4. Select only what you need
SELECT * on wide tables — especially with large text or JSON columns — moves a lot of data you never show. For lists and APIs, select the columns you need, and paginate:
Product::select('id', 'name', 'price', 'slug')->paginate(24);
For large exports, use chunkById() or lazyById() instead of loading everything into memory.
5. Queue anything slow
Sending email, resizing images, calling third-party APIs and generating PDFs don’t need to happen before the user sees a response.
Mail::to($order->customer)->queue(new OrderConfirmation($order));
ProcessProductImages::dispatch($product);
Use Redis or the database queue driver, and run workers under Supervisor — see common Laravel deployment problems for the setup.
6. Cache deliberately
Cache things that are expensive to compute and change rarely: category trees, settings, dashboard totals.
$categories = Cache::remember('categories.tree', now()->addHour(), fn () =>
Category::with('children')->whereNull('parent_id')->get()
);
Clear or refresh the cache when the underlying data changes (for example, in a model observer). Caching data that changes every request just adds complexity.
7. Use Laravel’s production caches and OPcache
These take one deploy step and help every request:
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
And make sure PHP’s OPcache is enabled on the server, so PHP doesn’t recompile your code on each request. Running composer install --optimize-autoloader --no-dev also trims autoload overhead.
8. Don’t forget the front end
A fast backend doesn’t help if the page ships 5 MB of images. On most sites I review, the biggest front-end wins are:
- serving images in WebP/AVIF at the size they’re displayed,
- lazy-loading images below the fold,
- removing unused JavaScript libraries,
- putting static assets behind a CDN such as Cloudflare.
Tools like Compressly Pro exist precisely because oversized images are so common.
9. Only then, scale the infrastructure
If the application is still slow after all of this, look at infrastructure: a Redis cache, read replicas for reporting, Laravel Octane for high request volumes, or simply a larger database instance. At that point you’ll know exactly what you’re scaling and why.
The short version
- Measure.
- Remove N+1 queries.
- Add indexes that match real queries.
- Select and paginate.
- Queue slow work.
- Cache what’s expensive and stable.
- Enable production caches and OPcache.
- Fix front-end weight.
- Scale infrastructure last.
If you have a Laravel application that has become slow, this is the kind of work I do — see Laravel development in Nepal.
Related projects
- Compressly Pro — Image optimisation product
- Medhey — E-commerce & services marketplace
Technologies
- Laravel →
- Performance
- PHP