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.

Laravel 12 Performance Tips: Optimize Your App for Lightning-Fast Loading

Table Of Content

1 Prerequisites

Before starting, make sure you have the following set up on your development machine:
  • 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

In this guide, you'll discover 12 practical performance tips with complete code examples that can dramatically speed up your Laravel 12 application — often achieving 50-80% faster response times.

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)

The single fastest win you can get in any Laravel 12 production deployment is enabling framework-level caching. These commands precompile your configuration, routes, views, and events into optimized cached files — eliminating the need to parse them on every request.

    
        # 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

Optimize Composer's autoloader:
    
        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

The N+1 query problem is still the #1 performance killer in most Laravel apps. It happens when you load a collection and then access a relationship inside a loop — triggering a separate query for each item.

❌ 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

Laravel 12 improves memory management for large datasets. Here's how to leverage these enhancements effectively:

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)

For production applications, file-based caching won't scale. Switch to Redis (recommended) or Memcached for dramatically faster cache reads/writes.

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)

Heavy tasks like sending emails, generating PDFs, processing images, or syncing data should never run during the HTTP request cycle. Push them to background queues instead. Laravel Horizon provides a beautiful dashboard and powerful configuration for Redis-powered queues.

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)

In Laravel 12, Vite remains the default frontend build tool (replacing the older Laravel Mix). Properly configuring Vite for production can significantly reduce your page load size.

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+)

php.ini (strongly recommended for production)
    
        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

Proper database indexing is one of the most impactful optimizations you can make. Without indexes, MySQL performs full table scans — which get exponentially slower as your data grows.

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)

Compressing HTTP responses can reduce transfer sizes by 60-80%, significantly improving load times — especially for users on slower connections. Both Gzip and Brotli are widely supported by modern browsers.

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

Serving static assets (images, CSS, JS, fonts) through a Content Delivery Network (CDN) dramatically reduces latency by serving files from edge servers closest to your users.

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

Optimization isn't a one-time task — you need ongoing monitoring to catch regressions and new bottlenecks as your application grows.

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
Want to take your Laravel 12 skills further? Here are our most popular Laravel 12 guides:

15 Conclusion

By combining Laravel 12's built-in performance improvements with these 12 proven optimization techniques — especially caching, eager loading, queues, and proper indexing — most applications can achieve 50-80% faster response times and handle significantly more traffic on the same infrastructure.

Quick-win priority order:
  1. Enable route & config caching — 5 minutes of work, immediate impact
  2. Fix N+1 queries with eager loading and preventLazyLoading()
  3. Switch to Redis for caching and sessions
  4. Enable OPcache + JIT on your production server
  5. Add database indexes for your most frequent queries

Continue learning:

Have questions or additional tips? Share them in the comments below!
Revathi M - PHP and CodeIgniter Developer

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).