Most slow Laravel pages have the same two causes: the same heavy query runs on every request, or the user waits for work that could happen later. Caching fixes the first. Queues fix the second.
Cache expensive queries#
Cache::remember runs the query once and keeps the result for the time you choose:
$stats = Cache::remember('dashboard:stats', now()->addMinutes(10), function () {
return [
'orders' => Order::whereDate('created_at', today())->count(),
'revenue' => Order::whereDate('created_at', today())->sum('total'),
];
});
Clear the key when the data changes, for example in a model observer, so users never see stale numbers for long.
Fix N+1 queries first#
Caching hides problems; eager loading removes them. Load relations up front:
$posts = Post::with(['author', 'tags'])->latest()->paginate(20);
Add Model::preventLazyLoading(! app()->isProduction()); to AppServiceProvider and Laravel will warn you during development whenever a relation loads in a loop.
Move slow work to a queue#
Sending email, resizing images and calling external APIs should not delay the response. Dispatch a job instead:
class SendInvoice implements ShouldQueue
{
use Queueable;
public function __construct(public Order $order) {}
public function handle(): void
{
Mail::to($this->order->customer)->send(new InvoiceMail($this->order));
}
}
SendInvoice::dispatch($order);
Run a worker with php artisan queue:work, managed by Supervisor or your host's process manager, so it restarts if it stops.
Production quick wins#
- Run
php artisan optimizeon deploy to cache config, routes and views. - Use Redis for cache and queues when your host supports it.
- Enable OPcache in PHP.
- Add database indexes for columns you filter and sort by.
Measure before and after each change. Laravel Debugbar or Telescope shows exactly which queries a page runs.




