Engineering January 28, 2026 4 min read Updated Jan 28, 2026

Laravel Performance Guide: Faster Pages, Cheaper Servers, Happier Users

A practical Laravel performance playbook: fix slow queries, use caching and queues correctly, and scale without guessing.

OT

OSCORP Team

Backend Engineering

Laravel Performance Optimization Caching Queues Database Indexing Scaling PHP
Laravel Performance Guide: Faster Pages, Cheaper Servers, Happier Users

Highlights

Summary

Highlights

Executive summary

A practical Laravel performance playbook: fix slow queries, use caching and queues correctly, and scale without guessing.

Laravel performance issues usually come from a few repeat patterns: N+1 database queries, unindexed filters, heavy work done in HTTP requests, and caching that’s either missing or unsafe. The good news is you don’t need a rewrite to get big wins. You need measurement, a small set of proven tactics, and discipline around what runs synchronously. This guide shows a step-by-step approach: find your slow endpoints, fix query patterns, add the right indexes, cache what’s expensive but stable, move heavy tasks to queues, and set simple performance budgets (p95 latency targets). Done right, you’ll ship faster pages, reduce infrastructure costs, and keep performance stable as usage grows.

Quick checklist

Skim
  • Measure p95 latency + slow queries before changing anything
  • Fix N+1 and add missing indexes on filtered columns
  • Cache expensive reads (tenant/user safe keys)
  • Move heavy work to queues (email, exports, reports)

Section highlights

Measure first (find the real bottleneck)

  • Identify top 5 slow endpoints by p95 latency
  • Log slow queries and request durations
  • Separate DB time vs app time vs external API time
  • Set a simple target (e.g., p95 < 400–700ms for core pages)

Database wins (N+1, indexes, and query shape)

  • Fix N+1 with eager loading and query constraints
  • Index columns used in WHERE, JOIN, ORDER BY
  • Avoid SELECT * and fetch only what you need
  • Use pagination and limit large dataset scans

Caching (safe, correct, and tenant-aware)

  • Cache expensive computations and repeated reads
  • Use proper cache keys (user/tenant scoped)
  • Invalidate intentionally (TTL + events), not randomly
  • Cache views/config/routes in production deployments

Queues and background jobs (make HTTP fast again)

  • Offload email, webhooks, exports, image processing
  • Use separate queues for heavy vs light jobs
  • Add retries, timeouts, and idempotency to jobs
  • Monitor queue lag and failed jobs to prevent silent backlog
On this page

Performance is a product feature

Users don’t experience your architecture—they experience waiting. Slow pages reduce trust, increase drop-off, and raise support costs. The best part: most Laravel apps can get 2–10x faster with focused fixes—no rewrite needed.

The common mistake is jumping into “optimization mode” without measurement. Performance work should follow a simple order:

  1. measure the bottleneck

  2. fix the biggest offenders

  3. add guardrails so it doesn’t regress


Step 1 — Measure before you optimize

Start with clarity:

What to measure

  • p95 response time for key endpoints (not just average)

  • slow queries (duration + frequency)

  • error rate and timeouts

  • external dependencies (payment gateway, SMS, email)

A simple target

For most SaaS dashboards:

  • core pages: p95 under ~400–700ms

  • heavy reports: async or “generate in background”

If your app is slower, it’s usually DB or doing heavy work inside the request.


Step 2 — Kill the N+1 problem (the #1 Laravel slowdown)

N+1 happens when you load a list and then query related data per row.

Example symptom

  • loading 25 rows triggers 26+ queries

  • response time grows linearly with list size

Fix it with eager loading

Use eager loading with constraints so you fetch relationships efficiently.

// Bad: triggers N+1 in loops
$loans = Loan::latest()->take(25)->get();
foreach ($loans as $loan) {
  echo $loan->client->name;
}

// Good: eager load relationship
$loans = Loan::with('client')
  ->latest()
  ->take(25)
  ->get();

Pro tip: eager load only what you need. Don’t load 10 relationships “just in case.”


Step 3 — Index the columns you filter on

If your pages filter by status, client_id, created_at, tenant_id, those columns should often be indexed.

What should be indexed (typical)

  • foreign keys (client_id, user_id, tenant_id)

  • frequently filtered columns (status, type)

  • sorting keys (created_at) when used with filters

Quick sanity check

If a query scans thousands of rows for every request, your DB is doing unnecessary work.


Step 4 — Shape your queries (don’t over-fetch)

Small changes = big wins:

  • Avoid SELECT * on large tables

  • Only select needed fields (select('id','name'))

  • Use pagination instead of loading all rows

  • Avoid heavy joins inside “hot” endpoints

  • Cache computed aggregates instead of recalculating each request


Step 5 — Cache the right things (and don’t leak data)

Caching works best for:

  • expensive reads that don’t change every second

  • computed values (counts, summaries, dashboards)

  • config/routes/views in production build steps

Safe cache key rule

If your app is multi-tenant or user-specific, cache keys must include scope.

// Bad: may leak across users/tenants
Cache::remember("dashboard_stats", 60, fn() => $stats);

// Good: tenant/user scoped keys
Cache::remember("tenant:$tenantId:user:$userId:dashboard_stats", 60, fn() => $stats);

Invalidation strategy

Don’t overthink it:

  • use TTL for most caches

  • use event-based invalidation for critical values

  • avoid “clear all caches” as a normal workflow


Step 6 — Move heavy work to queues

Any work that doesn’t need to complete inside the HTTP request should be queued:

  • emails and notifications

  • report generation

  • data imports/exports

  • image processing

  • third-party API sync

A simple job example

dispatch(new GenerateMonthlyReport($tenantId))->onQueue('heavy');

Queue best practices

  • separate heavy vs default queues

  • retries + timeouts set explicitly

  • jobs should be idempotent (safe to retry)


Step 7 — Laravel production deployment optimizations

These are “free wins” you should do in production:

  • php artisan config:cache

  • php artisan route:cache

  • php artisan view:cache

  • OPcache enabled in PHP-FPM

  • ensure queue workers are running and monitored

(And avoid APP_DEBUG=true in production.)


A simple performance sprint plan (copy)

Day 1–2: Measure
- identify top 5 slow endpoints (p95)
- enable slow query logging
- list top 10 slow queries by frequency

Day 3–5: Fix DB
- remove N+1 on hot endpoints
- add indexes for filtered columns
- paginate large lists

Day 6–7: Cache + queues
- cache expensive summaries
- move exports/reports/emails to queues
- monitor queue lag + failed jobs

Common mistakes (and quick fixes)

  • Caching without scoping → include tenant/user in cache keys

  • Optimizing the wrong endpoint → measure p95 first

  • Doing exports in HTTP → move to queue + notify user

  • No indexes on hot filters → add indexes and verify query plans

  • One queue for everything → split heavy vs default


Closing

Laravel can scale beautifully when you treat performance as a system: measure, fix bottlenecks, cache safely, and move heavy work off the request path. Most apps don’t need microservices—they need disciplined performance hygiene.

If you want, OSCORP can run a performance audit and deliver:

  • top bottlenecks + quick wins

  • index and query improvements

  • caching + queue plan

  • rollout checklist to prevent regressions

Share



Related posts

View all