Introduction
Application performance directly impacts user experience, conversion rates, and SEO rankings. A slow Laravel application can frustrate users, increase bounce rates, and cost your business revenue. Studies show that a one-second delay in page load time can reduce conversions by 7% and page views by 11%.
Laravel is a powerful framework, but without proper optimization, even well-architected applications can suffer from performance bottlenecks. Whether you're handling thousands of concurrent users or optimizing for mobile devices, performance optimization should be a priority from day one.
In this comprehensive guide, we'll explore proven techniques to optimize your Laravel 12 application, covering caching strategies, database optimization, query performance, asset management, and server configuration. These techniques are battle-tested across production applications serving millions of users.
By implementing these optimization strategies, you can dramatically reduce response times, minimize server load, and deliver exceptional user experiences.
Why Laravel Performance Matters
The Business Impact
Performance affects multiple business metrics:
- User Experience – Faster apps keep users engaged
- SEO Rankings – Google prioritizes fast-loading sites
- Conversion Rates – Speed directly impacts sales
- Server Costs – Efficient code reduces infrastructure expenses
- Scalability – Optimized apps handle more traffic with fewer resources
Common Performance Bottlenecks
Laravel applications typically suffer from:
- N+1 Query Problems – Loading relationships inefficiently
- Missing Cache Layers – Repeating expensive operations
- Unoptimized Database Queries – Slow or missing indexes
- Heavy Asset Files – Large JavaScript and CSS bundles
- Memory Leaks – Inefficient resource management
- Synchronous Processing – Blocking operations in request cycles
Configuration Optimization
1. Enable Configuration Caching
Cache configuration files to eliminate file reads on every request:
php artisan config:cache
This combines all configuration files into a single cached file. Critical for production:
// In production deployment script
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
Important: After changing .env or config files, clear and rebuild the cache:
php artisan config:clear
php artisan config:cache
2. Route Caching
Cache route definitions for instant route matching:
php artisan route:cache
This converts routes to a single serialized array, dramatically improving routing performance.
Note: Route caching doesn't work with closure-based routes. Use controller methods instead:
// ❌ Avoid closures in production
Route::get('/dashboard', function () {
return view('dashboard');
});
// ✅ Use controllers
Route::get('/dashboard', [DashboardController::class, 'index']);
3. Optimize Autoloading
Generate optimized Composer autoload files:
composer install --optimize-autoloader --no-dev
Or for existing installations:
composer dump-autoload --optimize
4. View Caching
Pre-compile Blade templates to PHP:
php artisan view:cache
This eliminates template compilation on every request.
Database Query Optimization
1. Solve N+1 Query Problems
The N+1 problem occurs when loading relationships in loops. Use eager loading:
// ❌ N+1 Problem: 1 query + N queries for authors
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Query executed for each post
}
// ✅ Eager Loading: Only 2 queries total
$posts = Post::with('author')->get();
foreach ($posts as $post) {
echo $post->author->name;
}
Load multiple relationships:
$posts = Post::with(['author', 'comments.user', 'tags'])->get();
Use withCount() for relationship counts:
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count; // No additional queries
}
2. Select Only Required Columns
Avoid SELECT * by specifying needed columns:
// ❌ Loads all columns (inefficient)
$users = User::all();
// ✅ Load only needed columns
$users = User::select('id', 'name', 'email')->get();
When using relationships:
$posts = Post::with('author:id,name,email')
->select('id', 'title', 'author_id', 'created_at')
->get();
3. Use Database Indexes
Add indexes to frequently queried columns:
Schema::table('posts', function (Blueprint $table) {
$table->index('user_id');
$table->index('status');
$table->index('published_at');
$table->index(['status', 'published_at']); // Composite index
});
Analyze slow queries and add indexes accordingly:
EXPLAIN SELECT * FROM posts WHERE status = 'published' AND published_at > '2024-01-01';
4. Chunk Large Result Sets
Process large datasets in batches to reduce memory usage:
// ❌ Loads all records into memory
User::all()->each(function ($user) {
// Process user
});
// ✅ Processes in chunks of 1000
User::chunk(1000, function ($users) {
foreach ($users as $user) {
// Process user
}
});
// ✅ Even better: Lazy loading with cursor
User::cursor()->each(function ($user) {
// Process user without loading all into memory
});
5. Implement Query Caching
Cache expensive database queries:
// Cache for 1 hour
$posts = Cache::remember('popular_posts', 3600, function () {
return Post::where('views', '>', 1000)
->orderBy('views', 'desc')
->limit(10)
->get();
});
Use cache tags for better control:
$posts = Cache::tags(['posts', 'popular'])->remember('popular_posts', 3600, function () {
return Post::popular()->get();
});
// Clear specific cache tags
Cache::tags(['posts'])->flush();
6. Use Raw Queries When Needed
For complex queries, raw SQL can be more efficient:
$results = DB::select('
SELECT
posts.*,
COUNT(comments.id) as comments_count,
AVG(ratings.score) as average_rating
FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
LEFT JOIN ratings ON posts.id = ratings.post_id
WHERE posts.published_at >= ?
GROUP BY posts.id
HAVING comments_count > ?
ORDER BY average_rating DESC
LIMIT 20
', [now()->subDays(30), 5]);
Caching Strategies
1. Application Cache
Laravel supports multiple cache drivers. Redis is recommended for production:
CACHE_DRIVER=redis
Install Redis PHP extension:
composer require predis/predis
2. Cache Configuration
Configure Redis in config/database.php:
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
3. Model Caching
Cache entire model results:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
public static function getCached($id)
{
return Cache::remember("post.{$id}", 3600, function () use ($id) {
return static::with(['author', 'tags'])->findOrFail($id);
});
}
protected static function booted()
{
// Clear cache when model is updated
static::updated(function ($post) {
Cache::forget("post.{$post->id}");
});
static::deleted(function ($post) {
Cache::forget("post.{$post->id}");
});
}
}
4. Response Caching
Cache entire HTTP responses:
Route::middleware('cache.headers:public;max_age=3600')->group(function () {
Route::get('/api/posts', [PostController::class, 'index']);
});
Or use a package like spatie/laravel-responsecache:
composer require spatie/laravel-responsecache
Route::get('/posts', [PostController::class, 'index'])
->middleware('responsecache:3600');
5. OPcache Configuration
Enable PHP OPcache for dramatic performance gains. In php.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 # Disable in production
opcache.revalidate_freq=0
opcache.fast_shutdown=1
Queue Optimization
1. Offload Heavy Tasks
Move time-consuming operations to queues:
// ❌ Synchronous email sending (slow)
public function store(Request $request)
{
$order = Order::create($request->validated());
Mail::to($order->customer)->send(new OrderConfirmation($order));
return redirect()->route('orders.show', $order);
}
// ✅ Queued email sending (fast)
public function store(Request $request)
{
$order = Order::create($request->validated());
Mail::to($order->customer)->queue(new OrderConfirmation($order));
return redirect()->route('orders.show', $order);
}
2. Use Horizon for Queue Management
Install Laravel Horizon for Redis queues:
composer require laravel/horizon
php artisan horizon:install
Configure in config/horizon.php:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'maxProcesses' => 10,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 256,
'tries' => 3,
'timeout' => 300,
],
],
],
3. Job Batching
Process multiple jobs together:
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch([
new ProcessOrder($order1),
new ProcessOrder($order2),
new ProcessOrder($order3),
])->then(function (Batch $batch) {
// All jobs completed successfully
})->catch(function (Batch $batch, Throwable $e) {
// First batch job failure detected
})->finally(function (Batch $batch) {
// The batch has finished executing
})->dispatch();
Asset Optimization
1. Vite Configuration
Laravel 12 uses Vite for asset bundling. Optimize vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'axios'],
},
},
},
chunkSizeWarningLimit: 1000,
cssCodeSplit: true,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
},
},
},
});
2. Image Optimization
Use Laravel's image optimization:
composer require spatie/laravel-image-optimizer
Configure in config/image-optimizer.php:
'optimizers' => [
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
'-m85', // Maximum quality
'--strip-all',
'--all-progressive',
],
Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
'--force',
'--quality=85-100',
],
],
Use in your code:
use Spatie\ImageOptimizer\OptimizerChainFactory;
$optimizerChain = OptimizerChainFactory::create();
$optimizerChain->optimize($pathToImage);
3. Lazy Loading Images
Implement lazy loading in Blade templates:
<img src="{{ asset('images/placeholder.jpg') }}"
data-src="{{ $post->image_url }}"
alt="{{ $post->title }}"
loading="lazy"
class="lazyload">
4. CDN Integration
Serve static assets from a CDN:
ASSET_URL=https://cdn.yourdomain.com
In config/app.php:
'asset_url' => env('ASSET_URL'),
Session and Cookie Optimization
1. Use Redis for Sessions
Configure Redis as session driver:
SESSION_DRIVER=redis
2. Reduce Session Data
Store only essential data in sessions:
// ❌ Storing large objects
session(['user_data' => $user->load('posts', 'comments', 'followers')]);
// ✅ Store minimal data
session(['user_id' => $user->id]);
Memory Management
1. Unset Large Variables
Free memory after processing large datasets:
$largeDataset = DB::table('large_table')->get();
// Process data
foreach ($largeDataset as $item) {
// Process item
}
// Free memory
unset($largeDataset);
2. Use Generators
For memory-efficient iteration:
function getUsers()
{
foreach (User::cursor() as $user) {
yield $user;
}
}
foreach (getUsers() as $user) {
// Process user with minimal memory
}
Server Configuration
1. PHP-FPM Optimization
Configure php-fpm.conf:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
2. Nginx Configuration
Optimize nginx.conf:
server {
listen 80;
server_name example.com;
root /var/www/html/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/x-javascript application/xml+rss
application/json application/javascript;
# Browser caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Monitoring and Profiling
1. Laravel Telescope
Install Telescope for debugging:
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
2. Laravel Debugbar
Monitor query performance:
composer require barryvdh/laravel-debugbar --dev
3. Application Performance Monitoring
Use services like:
- New Relic – Comprehensive APM
- Blackfire.io – PHP profiling
- Laravel Pulse – Built-in monitoring
Install Laravel Pulse:
composer require laravel/pulse
php artisan pulse:install
Conclusion
Optimizing Laravel application performance is an ongoing process that combines proper configuration, efficient database queries, strategic caching, and server optimization. By implementing these techniques, you can significantly reduce response times, handle more concurrent users, and deliver exceptional user experiences.
Key takeaways:
- Enable configuration, route, and view caching in production
- Solve N+1 query problems with eager loading
- Implement multi-layer caching with Redis
- Offload heavy tasks to queues
- Optimize assets with Vite and CDN
- Monitor performance with Telescope and Pulse
- Configure PHP-FPM and web server properly
Need help optimizing your Laravel application? NeedLaravelSite specializes in performance audits and optimization services. Contact us for expert Laravel development and optimization.
Related Resources: