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 optimize on 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.