Complete Guide to Laravel 12 Performance Optimization with Code Examples
Is your Laravel 12 application running slower than expected? You're not alone — performance is one of the most common challenges developers face when scaling Laravel projects.Laravel is a powerful PHP framework that simplifies web development, but without proper optimization, even well-structured applications can suffer from slow loading times, high server costs, and poor user experience. A delay of just 1 second can increase bounce rates by 32% and negatively impact your SEO rankings.
Thankfully, Laravel 12 (released February 2025) brings several under-the-hood performance refinements, including better query execution, improved service container efficiency, smarter job batching, and reduced boot time. If you're new to Laravel 12, check out our complete upgrade guide from Laravel 11 to 12 first.
In this comprehensive guide, we'll walk you through 12 battle-tested optimization techniques — each with real code examples — that can dramatically improve your application's speed and scalability. Whether you're running a small blog or a high-traffic SaaS platform, these tips apply to every Laravel 12 project.

Table Of Content
1 Prerequisites
- PHP ≥ 8.2 (PHP 8.3+ recommended for JIT support)
- Composer (latest version)
- Laravel 12 installed — see our Laravel 12 installation & upgrade guide
- MySQL / PostgreSQL / SQLite (or any supported database)
- Basic knowledge of Laravel routing, controllers, and Blade templates
- Redis (recommended for caching and queues)
2 Introduction
We'll cover optimizations across every layer of your application stack:
- Framework-level — Config, route, and view caching
- Database-level — Eager loading, indexing, and query optimization
- Application-level — Redis caching, queue management with Horizon
- Server-level — OPcache, JIT compilation, Gzip/Brotli compression
- Frontend-level — Vite asset bundling and CDN delivery
Each tip includes before/after code examples so you can implement them immediately. Let's dive in!
3 Cache All The Things (Configuration, Routes, Views)
# Run once after deployment (or in CI/CD pipeline)
php artisan config:cache # Merges all config files into a single cached file
php artisan route:cache # Compiles routes into a faster serialized format
php artisan view:cache # Pre-compiles all Blade templates
php artisan event:cache # Caches event-listener mappings
# Clear everything when you make changes
php artisan optimize:clear
Use php artisan optimize (still useful in Laravel 12) to run several of these commands at once.Pro Tip: Add these commands to your CI/CD deployment script (e.g., GitHub Actions, Laravel Forge, or Envoyer) so caching happens automatically on every deployment. Never run config:cache during local development — it will cause .env changes to be ignored.
4 Use Route Caching + Compiled Classmap
composer install --optimize-autoloader --no-dev
In production composer.json, prefer classmap over PSR-4 for better performance:
"autoload": {
"classmap": [
"database/seeders",
"database/factories"
],
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
Then run:
composer dump-autoload --optimize --classmap-authoritative
5 Fix N+1 Query Problem with Eager Loading
❌ Bad (N+1 Problem):
// This runs 1 query for users + N queries for posts = N+1 total!
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}
✅ Good (Eager Loading):
// This runs only 2 queries total, regardless of how many users exist
$users = User::with(['posts', 'profile', 'roles'])->get();
foreach ($users as $user) {
echo $user->posts->count();
}
Laravel 12 Tip: Use Model::preventLazyLoading() in your AppServiceProvider to catch N+1 issues during development:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
This throws an exception whenever a lazy-loaded relationship is accessed, helping you find and fix every N+1 issue before it reaches production. For more Eloquent tips and tricks, check our dedicated guide. 6 Use Laravel 12's Improved Query Optimizations
Lazy Collections — Process millions of rows without running out of memory:
// Lazy collections hydrate one model at a time (improved in Laravel 12)
User::lazy()->each(function (User $user) {
ProcessUserJob::dispatch($user);
});
Chunking — Process records in manageable batches:
// Process 500 users at a time to control memory usage
User::where('active', true)->chunk(500, function ($users) {
foreach ($users as $user) {
// process each user
}
});
Cursor — Stream results one at a time (lowest memory usage):
// Cursor uses PHP generators — ideal for exports or batch jobs
foreach (User::where('active', true)->cursor() as $user) {
// Only one model in memory at a time
}
When to use which? Use chunk() when you need to update records (it uses OFFSET). Use cursor() for read-only operations like exports. Use lazy() as a general-purpose memory-efficient alternative to get().If you're processing data in the background, combine these with Laravel 12 queues and jobs for maximum performance.
7 Implement Proper Caching (Redis/Memcached)
Step 1: Set Redis as default cache driver in config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
Step 2: Cache expensive queries with Cache::remember()
// Cache dashboard stats for 10 minutes — avoids hitting the DB on every page load
$stats = Cache::remember('dashboard_stats', now()->addMinutes(10), function () {
return [
'users' => User::count(),
'revenue' => Order::sum('total'),
'top_users' => User::withCount('orders')
->orderByDesc('orders_count')
->take(5)
->get(),
];
});
Step 3: Use cache tags for granular invalidation (Redis only)
// Tag related cache entries
Cache::tags(['users', 'dashboard'])->put('stats', $stats, 600);
// Flush only user-related caches when a user is updated
Cache::tags(['users'])->flush();
Pro Tip: Always set a TTL (time-to-live) on your cache entries. Permanent caches can cause stale data bugs that are hard to debug. 8 Optimize Queues with Horizon (Redis)
Install Horizon:
composer require laravel/horizon
php artisan horizon:install
config/horizon.php — Configure multiple supervisors with auto-balancing:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'auto', // Auto-balance workers based on queue load
'processes' => 10,
'tries' => 3,
'timeout' => 90,
],
'supervisor-low' => [
'connection' => 'redis',
'queue' => ['low'],
'balance' => 'simple',
'processes' => 5,
'tries' => 5,
'timeout' => 300, // Longer timeout for heavy jobs
],
],
],
For a deep dive into queue setup, dispatching jobs, and handling failures, read our Laravel 12 Queue and Jobs Tutorial. 9 Asset Optimization (Vite in Laravel 12)
Build for production:
npm run build
Enable aggressive compression in vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [laravel(['resources/css/app.css', 'resources/js/app.js'])],
build: {
minify: 'esbuild', // Fastest minifier
cssMinify: true, // Minify CSS output
sourcemap: false, // Disable sourcemaps in production
target: 'es2022', // Modern JS for smaller output
rollupOptions: {
output: {
manualChunks: undefined, // Let Vite optimize chunking
},
},
},
});
Additional tips:- Use defer or async attributes for non-critical scripts
- Lazy-load images with loading="lazy"
- Use @vite directive in Blade — it automatically handles asset versioning and cache-busting
10 Use OPcache + JIT (PHP 8.3+)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=0 ; production = 0
opcache.jit=tracing ; or 1255 for maximum performance
opcache.jit_buffer_size=100M
11 Database Indexing & Query Tuning
Add indexes for columns used in WHERE, ORDER BY, and JOIN clauses:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->index();
$table->string('slug')->unique();
$table->string('status')->index();
$table->timestamps();
// Composite index for queries like: WHERE user_id = ? ORDER BY created_at DESC
$table->index(['user_id', 'created_at']);
});
Use EXPLAIN to analyze slow queries:
// In Tinker or a test route
DB::enableQueryLog();
$posts = Post::where('user_id', 1)->orderBy('created_at', 'desc')->get();
// Check the query log
dd(DB::getQueryLog());
// Or use EXPLAIN directly
$explain = DB::select('EXPLAIN SELECT * FROM posts WHERE user_id = 1 ORDER BY created_at DESC');
Pro Tip: Install Laravel Telescope or Debugbar during development to automatically flag slow queries (>100ms) and missing indexes. 12 Compress Responses (Gzip/Brotli)
Nginx Configuration (recommended):
# Enable Gzip compression
gzip on;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 256;
gzip_vary on;
# Enable Brotli (if ngx_brotli module is installed)
# brotli on;
# brotli_comp_level 6;
# brotli_types text/plain text/css application/json application/javascript;
Laravel Middleware Alternative:If you don't have access to your web server config, you can also use Laravel middleware or the spatie/laravel-responsecache package to cache and compress full responses.
How to verify: Open Chrome DevTools → Network tab → check the Content-Encoding response header. It should show gzip or br.
13 Use CDN for Static Assets
Popular CDN options for Laravel:
- Cloudflare — Free tier available, easy DNS-level setup, includes DDoS protection
- BunnyCDN — Affordable pay-as-you-go pricing, excellent performance
- AWS CloudFront — Best for apps already on AWS infrastructure
Configure Vite to use CDN URL prefix in production:
// vite.config.js
export default defineConfig({
plugins: [laravel(['resources/css/app.css', 'resources/js/app.js'])],
base: process.env.ASSET_URL || '/', // Set ASSET_URL to your CDN domain
});
Set the asset URL in your .env file:
ASSET_URL=https://cdn.yourdomain.com
All @vite and asset() calls will now automatically point to your CDN. 14 Monitor & Profile Continuously
Development Tools:
# Laravel Telescope — Full request/query/job inspector
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
Production Monitoring:- Laravel Pulse — Lightweight, built-in production monitoring (new in Laravel ecosystem). Shows slow queries, slow requests, cache hit rates, and queue throughput.
- Laravel Horizon — Real-time queue dashboard for Redis-powered queues. Monitor job throughput, failures, and wait times.
- Blackfire.io — Deep performance profiling. Identifies exact bottlenecks down to individual function calls.
Key metrics to track:
- Average response time (target: <200ms for API, <500ms for pages)
- Database query count per request (target: <10 queries)
- Memory usage per request
- Cache hit ratio (target: >90%)
- Queue job processing time and failure rate
Bonus: Recommended Reading for Laravel 12 Developers
- 🚀 How to Upgrade from Laravel 11 to Laravel 12 Without Breaking Changes — Step-by-step migration guide with compatibility checklist.
- 📡 How to Build a REST API in Laravel 12 — Complete tutorial with authentication, validation, and best practices.
- ⚡ Laravel 12 Queue and Jobs Tutorial — Master background processing for better app performance.
- 🔐 Best Authentication Options in Laravel 12 — Comprehensive authentication setup with WorkOS AuthKit.
- 🧠 Laravel 12 Eloquent Tips and Tricks — Essential ORM patterns every developer should know.
- 🛤️ Laravel 12 Routing and Middleware Guide — Complete routing and middleware reference with code snippets.
- 🐛 Common Laravel 12 Errors and How to Fix Them — Troubleshooting guide for the most frequent Laravel 12 issues.
- 🐳 Docker Compose Setup for Laravel & MySQL — Containerize your Laravel development environment.
15 Conclusion
Quick-win priority order:
- Enable route & config caching — 5 minutes of work, immediate impact
- Fix N+1 queries with eager loading and preventLazyLoading()
- Switch to Redis for caching and sessions
- Enable OPcache + JIT on your production server
- Add database indexes for your most frequent queries
Continue learning:
- Laravel 12 Eloquent Tips and Tricks for Beginners
- Laravel 12 Queue and Jobs Tutorial
- Laravel 12 Routing and Middleware Complete Guide
- Common Laravel 12 Errors and How to Fix Them
- How to Build a REST API in Laravel 12
Have questions or additional tips? Share them in the comments below!
Written by Revathi M
PHP Developer & Technical Writer · 10+ years building web applications with CodeIgniter and Laravel
Revathi specializes in PHP backend development, authentication systems, and REST API design. She writes practical, production-tested tutorials at Get Sample Code to help developers build secure applications faster.
Frequently Asked Questions
Use Model::preventLazyLoading() in your AppServiceProvider boot method during development. This throws an exception whenever a lazy-loaded relationship is accessed, helping you identify every N+1 issue. For production monitoring, use Laravel Telescope or Debugbar to flag slow or excessive queries.
Yes, OPcache is strongly recommended for any PHP production server running Laravel 12. It caches compiled PHP bytecode in memory, eliminating the need to recompile scripts on every request. Combined with JIT (Just-In-Time) compilation available in PHP 8.3+, you can see 20-40% faster execution for CPU-intensive operations.
chunk() processes records in batches using OFFSET queries — best for update operations. cursor() uses PHP generators to stream one record at a time with minimal memory — ideal for read-only exports. lazy() returns a LazyCollection that hydrates models one at a time — a general-purpose memory-efficient alternative to get(). Choose based on whether you need to write data back and how much memory efficiency you require.
Laravel 12 brings better query execution, a faster service container, improved job batching with reduced overhead, faster boot time, smarter indexing on Eloquent models, and better memory management for large datasets. The framework also benefits from PHP 8.3+ JIT compilation for additional speed gains.
Most real-world applications see 50-80% faster response times after implementing the key optimizations: route/config caching, eager loading to fix N+1 queries, Redis for caching and sessions, queue management with Horizon, and proper database indexing. The exact improvement depends on your current setup and traffic patterns.
Redis is generally recommended for Laravel 12 in 2026 because it supports rich data types, built-in queue management (with Laravel Horizon), pub/sub messaging, data persistence, and atomic operations. Use Memcached only if you need purely key-value caching with the lowest possible latency and no persistence requirements.
Yes — route caching works perfectly with parameters. Just remember to clear cache after route changes with `php artisan route:clear`.
Laravel Pulse (lightweight production monitoring), Laravel Telescope + Debugbar (development), Blackfire.io (deep profiling), Laravel Horizon (queue monitoring).
