After a decade of building and rescuing Laravel applications, I can tell you that most slow Laravel apps suffer from the same handful of problems. The good news: each one has a well-understood fix. Here are the techniques I apply on every production project, roughly in the order of impact.
The single most common Laravel performance bug is the N+1 query: looping over models and lazily loading a relationship on each iteration. A page that should run 3 queries ends up running 300. Use with() to eager load relationships, and enable Model::preventLazyLoading() in non-production environments so N+1s throw instead of silently degrading your app.
Query results that change rarely — settings, menus, plan limits, dashboards — belong in Redis via Cache::remember(). The discipline is in invalidation: pair every cache write with a clear strategy (model observers or cache tags) so you never serve stale data after an update.
Emails, PDF generation, image processing, webhook calls, and third-party API requests should never run inside the HTTP request. Move them to queued jobs backed by Redis and run workers under Supervisor. Your p95 response time will drop immediately, and failures become retryable instead of user-facing errors.
Index the columns you filter and join on, paginate everything, and select only the columns you need on hot paths. Laravel's query log plus a tool like Telescope or Debugbar in staging will show you exactly where the time goes.
Measure first, optimize second. Every technique here is worthless if you apply it to the wrong bottleneck.
Applied together, these changes routinely take pages from over a second down to well under 200 ms. If your Laravel app is struggling under load, start with the query log — the answer is almost always there.