Skip to main content

Overview

Optimizing your Laravel application involves caching, database tuning, asset optimization, and proper server configuration. This guide covers essential optimization techniques.

Configuration Optimization

Cache Configuration Files

Combine all configuration files into a single cached file:
php artisan config:cache
This significantly reduces the number of file reads on each request.
After caching, the env() helper will only work within configuration files. Always use config() in your application code.

Clear Configuration Cache

When updating configuration, clear the cache:
php artisan config:clear
In development, avoid using config caching as it requires clearing after every change.

Route Optimization

Cache Routes

Cache your route registrations:
php artisan route:cache
This can dramatically speed up route registration on large applications.
Route caching doesn’t support closure-based routes. Convert all closure routes to controller methods:
// Don't cache - Closure route
Route::get('/users', function () {
    return User::all();
});

// Can cache - Controller route
Route::get('/users', [UserController::class, 'index']);

Clear Route Cache

php artisan route:clear

View Optimization

Compile Blade Templates

Precompile all Blade templates:
php artisan view:cache
This compiles all Blade views into PHP code, eliminating compilation on each request.

Clear View Cache

php artisan view:clear

Autoloader Optimization

Optimize Composer’s class autoloader:
composer install --optimize-autoloader --no-dev
Or for an existing installation:
composer dump-autoload --optimize
The --optimize flag generates a class map for faster class loading.

Asset Optimization

Vite Build Configuration

Your application uses Vite for asset compilation:
vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
        tailwindcss(),
    ],
    server: {
        watch: {
            ignored: ['**/storage/framework/views/**'],
        },
    },
});

Build for Production

Compile and optimize assets:
npm run build
{
  "scripts": {
    "build": "vite build",
    "dev": "vite"
  }
}
Vite automatically:
  • Minifies JavaScript and CSS
  • Tree-shakes unused code
  • Generates versioned filenames for cache busting
  • Optimizes images and assets
  • Creates source maps for debugging

Asset Serving

For production, serve assets through a CDN or configure proper caching headers:
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Database Optimization

Eager Loading

Avoid N+1 query problems by eager loading relationships:
// Executes 1 query + N queries (one per user)
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name;
}

Query Optimization

// Bad - Loads all columns
$users = User::all();

// Good - Only loads needed columns
$users = User::select('id', 'name', 'email')->get();

Database Indexing

Add indexes to frequently queried columns:
Schema::table('users', function (Blueprint $table) {
    $table->index('email');
    $table->index(['last_name', 'first_name']);
});

Connection Pooling

Use persistent database connections in production:
config/database.php
'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'forge'),
    'username' => env('DB_USERNAME', 'forge'),
    'password' => env('DB_PASSWORD', ''),
    'options' => [
        PDO::ATTR_PERSISTENT => true,
    ],
],

Caching Strategies

Application Cache

Cache::put('key', 'value', $seconds);

// Forever (until manually cleared)
Cache::forever('key', 'value');

// Remember (retrieve or store)
$value = Cache::remember('users', 3600, function () {
    return DB::table('users')->get();
});

Cache Drivers

Configure Redis for high-performance caching:
config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),

'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'lock_connection' => 'default',
    ],
],
.env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Response Caching

Cache entire HTTP responses:
Route::get('/posts', function () {
    return Cache::remember('posts.all', 3600, function () {
        return Post::with('author')->latest()->get();
    });
});
Or use response caching middleware:
Route::middleware('cache.response:3600')->group(function () {
    Route::get('/api/posts', [PostController::class, 'index']);
});

Queue Optimization

Use Queues for Slow Tasks

Offload time-consuming tasks to queues:
use App\Jobs\ProcessPodcast;

// Dispatch to queue
ProcessPodcast::dispatch($podcast);

// Dispatch with delay
ProcessPodcast::dispatch($podcast)
    ->delay(now()->addMinutes(10));

// Dispatch to specific queue
ProcessPodcast::dispatch($podcast)
    ->onQueue('processing');

Queue Workers

Run optimized queue workers:
# Development (from composer.json)
php artisan queue:listen --tries=1 --timeout=0

# Production
php artisan queue:work --tries=3 --timeout=90 --max-jobs=1000
The composer dev script includes queue workers:
"dev": [
    "Composer\\Config::disableProcessTimeout",
    "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
]

Redis Queue Driver

Use Redis for high-performance queues:
config/queue.php
'default' => env('QUEUE_CONNECTION', 'redis'),

'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 90,
        'block_for' => null,
    ],
],

Session Optimization

Use Redis or Memcached for session storage:
.env
SESSION_DRIVER=redis
config/session.php
'driver' => env('SESSION_DRIVER', 'redis'),

PHP Optimization

OPcache Configuration

Enable and configure OPcache for PHP:
php.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0  ; Disable in production
opcache.save_comments=1
opcache.fast_shutdown=1
Set opcache.validate_timestamps=0 in production and restart PHP after code changes.

PHP-FPM Configuration

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

Complete Optimization Script

Combine all optimizations:
optimize.sh
#!/bin/bash

echo "Starting optimization..."

# Clear old caches
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear

# Optimize Composer autoloader
composer dump-autoload --optimize

# Cache everything
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# Build assets
npm run build

# Restart services
php artisan queue:restart
sudo systemctl reload php8.2-fpm

echo "Optimization complete!"
Make it executable:
chmod +x optimize.sh
./optimize.sh

Development vs Production

# Use the dev script for hot module replacement
composer dev

# This runs:
# - php artisan serve
# - php artisan queue:listen
# - php artisan pail (logs)
# - npm run dev (Vite HMR)

Testing Environment

The test environment is optimized for speed:
phpunit.xml
<php>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
    <env name="CACHE_STORE" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
</php>
The testing environment uses:
  • In-memory SQLite database for fast tests
  • Array cache driver (no external dependencies)
  • Synchronous queue (runs immediately)
  • Reduced bcrypt rounds for faster hashing
Run tests efficiently:
# Use the test script
composer test

# Runs:
# - php artisan config:clear
# - php artisan test

Monitoring Performance

Laravel Telescope

Install Telescope for development monitoring:
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
Telescope is disabled in tests via phpunit.xml:
<env name="TELESCOPE_ENABLED" value="false"/>

Laravel Debugbar

Add Debugbar for request profiling:
composer require barryvdh/laravel-debugbar --dev

Query Logging

Log slow queries:
config/database.php
'connections' => [
    'mysql' => [
        // ...
        'options' => [
            PDO::ATTR_EMULATE_PREPARES => true,
        ],
        'slow_query_log' => env('DB_SLOW_QUERY_LOG', false),
        'slow_query_time' => 2000, // milliseconds
    ],
],

Benchmarking

Apache Bench

Test application performance:
# 1000 requests, 10 concurrent
ab -n 1000 -c 10 https://yourdomain.com/

Laravel Artisan Benchmark

Benchmark specific routes:
php artisan route:list --compact
php artisan optimize

Best Practices

1

Use caching strategically

  • Cache configuration, routes, and views in production
  • Use Redis for cache and sessions
  • Implement query result caching for expensive queries
2

Optimize database queries

  • Use eager loading to avoid N+1 problems
  • Add indexes to frequently queried columns
  • Select only needed columns
  • Use chunking for large datasets
3

Leverage queues

  • Move slow tasks to background jobs
  • Use Redis queue driver for performance
  • Configure proper queue workers with Supervisor
4

Optimize assets

  • Build assets for production with Vite
  • Serve assets through a CDN
  • Enable browser caching with proper headers
5

Monitor and measure

  • Use Laravel Telescope in development
  • Monitor slow queries and optimize them
  • Profile code with Debugbar
  • Benchmark critical endpoints
Always measure before and after optimization to ensure your changes have the desired effect.

Build docs developers (and LLMs) love