Performance optimization is what separates a Laravel app that survives traffic spikes from one that crumbles. After optimizing dozens of Laravel applications — some handling 10,000+ requests per minute — here are the 10 techniques that make the biggest difference.
1. Measure Before You Optimize
Never guess where the bottleneck is. Always measure first:
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrateTelescope shows you every request, query, exception, log, and job. Sort by "slowest queries" — that's your starting point. In production, use a combination of:
**The 80/20 rule**: 80% of performance issues come from 20% of the code — usually database queries and N+1 problems.
2. Eradicate N+1 Queries
This is the single most common performance killer in Laravel apps:
// BAD — N+1: 1 query for posts + 100 queries for users
$posts = Post::latest()->take(100)->get();
foreach ($posts as $post) {
echo $post->user->name;
}
// GOOD — 2 queries total
$posts = Post::with('user')->latest()->take(100)->get();Use Laravel's **lazy loading prevention** to catch these automatically:
// AppServiceProvider.php
use IlluminateDatabaseEloquentModel;
Model::preventLazyLoading(!app()->isProduction());This throws an exception when you access a relationship that wasn't eager-loaded. In 2025, there's no excuse for N+1 in production.
3. Redis Caching Strategy
Cache aggressively. Not just full-page cache — strategic caching at multiple levels:
// Level 1: Query result caching
$products = Cache::remember('products.active', 3600, function () {
return Product::where('active', true)
->with('category')
->get();
});
// Level 2: Fragment caching in Blade
@cache('user.profile.' . $user->id, 300)
<div class="profile-card">
{{ $user->bio }}
{{ $user->recent_activity }}
</div>
@endcache
// Level 3: Full-page cache for anonymous traffic
// Use Laravel Page Cache or Varnish**Cache invalidation is the hard part.** Use model events to invalidate intelligently:
class Product extends Model
{
protected static function booted()
{
static::saved(fn () => Cache::forget('products.active'));
static::deleted(fn () => Cache::forget('products.active'));
}
}4. Queue Heavy Work
If your HTTP response takes more than 200ms, offload work to queues:
// BAD — everything in the request cycle
class OrderController
{
public function store(Request $request)
{
$order = Order::create($request->validated());
$this->processPayment($order); // 500ms
$this->sendEmail($order); // 300ms
$this->updateInventory($order); // 200ms
return response()->json($order); // Total: 1s+
}
}
// GOOD — dispatch to queues
class OrderController
{
public function store(Request $request)
{
$order = Order::create($request->validated());
ProcessPayment::dispatch($order); // 5ms
SendOrderEmail::dispatch($order); // 5ms
UpdateInventory::dispatch($order); // 5ms
return response()->json($order); // Total: 50ms
}
}Configure queue workers for high throughput:
# Supervisor config — multiple workers per queue
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --queue=high,default --sleep=3 --tries=3 --max-time=3600
numprocs=8
autostart=true
autorestart=true5. Database Indexing Strategy
Indexes are your first line of defense. Check your slow query log:
-- Always index foreign keys
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Composite indexes for frequent WHERE + ORDER BY combinations
CREATE INDEX idx_orders_status_created ON orders(status, created_at);
-- Covering indexes for specific queries
CREATE INDEX idx_products_active_price ON products(active, price) INCLUDE (name, slug);Use Laravel's `explain()` to verify query plans:
DB::table('orders')
->where('status', 'processing')
->orderBy('created_at', 'desc')
->explain()
->dd();6. Octane: The Game-Changer
Laravel Octane supercharges your application by using Swoole or RoadRunner:
composer require laravel/octane
php artisan octane:install
php artisan octane:start --server=swoole --workers=8 --max-requests=1000| Metric | Without Octane | With Octane |
| Requests/sec | 300 | 4,200 |
| Memory/request | 18MB | 2MB (shared) |
| Concurrent users (same server) | 500 | 10,000+ |
Octane boots your app once and keeps it in memory. Every request reuses the same bootstrapped application. The result is 10-15x throughput improvement.
**Watch out for**: memory leaks. Octane requires clean code — static properties, singletons, and global state can leak between requests. Test thoroughly.
7. Optimize Asset Delivery
# Minify and version assets
php artisan filament:optimize # if using Filament
npm run build # Vite bundles and minifies
# Enable Brotli/Gzip compression (nginx)
gzip on;
gzip_types application/json text/css application/javascript;
gzip_comp_level 6;
# Cache control headers
location ~* .(css|js|jpg|png|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}8. Config and Route Caching
Never skip these in production:
php artisan config:cache # Merge all config into one file
php artisan route:cache # Pre-compile route registrations
php artisan view:cache # Pre-compile Blade templates
php artisan event:cache # Cache event discoveryEach reduces load time by 10-50ms. Combined, they can save 100ms+ per request.
9. Database Connection Pooling
For high-traffic apps, database connections are a bottleneck:
// config/database.php — use persistent connections
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'options' => [
PDO::ATTR_PERSISTENT => true, // Reuse connections
],
],For even better performance, use a connection pooler like **PgBouncer** (PostgreSQL) or **ProxySQL** (MySQL).
10. Monitoring and Alerting
You can't fix what you don't see:
// config/services.php — set up real alerts
'alerts' => [
'slack_webhook' => env('ALERT_SLACK_WEBHOOK'),
'email' => env('ALERT_EMAIL'),
],
// Define alert thresholds in AppServiceProvider
public function boot()
{
// Alert if queue backlog exceeds 1000
Queue::looping(function () {
$size = Queue::size();
if ($size > 1000) {
Notification::route('slack', config('services.alerts.slack_webhook'))
->notify(new QueueBackupAlert($size));
}
});
}Performance Checklist
Optimization is iterative. Start with the database, add caching, then move to application-level improvements. The goal isn't perfection — it's making your app fast enough that performance is never the bottleneck.