Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Performance Optimization: 15 Techniques That Work

By Kokil Thapa | Last reviewed: September 2026

Laravel performance optimization is not one magic switch. It is a stack of small, measurable changes applied in the right order. A booking portal that felt fine at launch can crawl once order history, media uploads, and admin reports grow. I've seen this on production Laravel applications where the code was correct but config, queries, and infrastructure were left at development defaults. This guide walks through 15 Laravel performance optimization techniques that actually ship results—not theoretical micro-optimisations you forget after deploy.

What does Laravel performance optimization cover in a real production stack?

Think in layers. Request handling sits on PHP, the framework bootstrap, your application code, the database, cache, queue workers, and the web server. A slow page is rarely one line of bad code. It is often five acceptable choices that compound.

On client projects I maintain with Deployer 7 and GitLab CI on Ubuntu, the same fixes recur after every major upgrade. Laravel 13.x on PHP 8.3 or 8.5 behaves differently from Laravel 12 on PHP 8.2. Always re-benchmark after a framework bump.

Start with measurement. Without a baseline, you cannot prove a change helped. Use Laravel Debugbar locally, Laravel Pulse in staging, or simple DB::listen() logging for query counts. Record TTFB, query count, and memory before you touch config.

Laravel Performance StackBrowser / CDNNginx / ApachePHP 8.5 + OPcachePHP-FPM poolLaravel 13 ApplicationRoute + config cacheRedis + MySQL 9.7Queue Workers
Laravel performance optimization spans the full request path—from CDN and web server through OPcache, framework caches, Redis, and background workers.

How do you optimize Laravel database queries and Eloquent performance?

Database work is usually the first bottleneck on data-heavy Laravel applications. These five techniques target query count, row volume, and index use.

Technique 1: Fix N+1 queries with eager loading

The classic N+1 problem loads related models inside a loop. One query becomes hundreds. Use with() for relationships you know you need.

/* Bad: N+1 on a booking index */
$bookings = Booking::latest()->paginate(20);
foreach ($bookings as $booking) {
    echo $booking->customer->name;
}

/* Good: two queries total */
$bookings = Booking::with(['customer', 'tour'])
    ->latest()
    ->paginate(20);

Install barryvdh/laravel-debugbar in local only. Watch the query tab on list pages. Anything above 10–15 queries on a simple index deserves investigation.

Technique 2: Add indexes that match real WHERE and JOIN clauses

Missing indexes show up once tables pass a few hundred thousand rows. Check slow query logs on MySQL 8.4 LTS or MySQL 9.7. Add composite indexes for filters you combine often.

Schema::table('bookings', function (Blueprint $table) {
    $table->index(['status', 'created_at']);
    $table->index('customer_id');
});

Do not index every column. Each index slows writes. Profile with EXPLAIN before and after.

Technique 3: Select only the columns you display

SELECT * pulls large text and JSON fields you never render. Narrow the select list on list views.

Booking::select(['id', 'reference', 'status', 'created_at'])
    ->with(['customer:id,name'])
    ->latest()
    ->paginate(20);

Eloquent needs the parent's key for eager loading. Include foreign keys in both select lists.

Technique 4: Chunk or cursor large exports instead of loading all rows

Spreadsheet exports and report generation should never call Model::all(). Use chunkById() or lazy() and stream output.

Booking::where('status', 'confirmed')
    ->chunkById(500, function ($bookings) use ($writer) {
        foreach ($bookings as $booking) {
            $writer->addRow($booking->toExportArray());
        }
    });

Pair this with a queued job so the HTTP request returns immediately.

Technique 5: Cache expensive aggregates with tagged cache invalidation

Dashboard totals that scan millions of rows belong in cache, not on every page load. Use Redis tags when your driver supports them.

$stats = Cache::tags(['bookings'])->remember('dashboard.stats', 300, function () {
    return [
        'confirmed' => Booking::where('status', 'confirmed')->count(),
        'revenue'   => Payment::whereMonth('created_at', now()->month)->sum('amount'),
    ];
});

Flush the tag when a booking or payment changes. Stale dashboard numbers erode trust faster than a one-second delay.

Query Optimization: N+1 vs Eager LoadBefore: N+11 query for list+ N queries per row21 queries for 20 rowsAfter: with()1 query for list1 query for relations2 queries for 20 rowsAlso apply: indexes, select(), chunkById()Index filtersstatus + dateNarrow SELECTid, name onlyCache totalsRedis tagsResult: lower TTFB and stable DB load
Laravel query optimization often cuts dozens of duplicate queries down to two with eager loading, indexes, and selective columns.

Which Laravel caching strategies deliver the biggest performance gains?

Caching is where Laravel performance optimization: 15 Techniques That Work pays off fastest. These five changes require minutes to deploy and seconds to verify.

Technique 6: Run artisan cache commands on every production deploy

Laravel compiles routes, config, views, and events into flat files. Skipping this step leaves the framework parsing PHP arrays on every request.

php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

Add these to your Deployer recipe after composer install. Reload PHP-FPM so OPcache picks up changed files. Never run config:cache if your config closures depend on runtime env() calls outside config files—that breaks production silently.

Technique 7: Tune PHP OPcache for production workloads

OPcache keeps compiled bytecode in memory. Default settings on Ubuntu are conservative. Raise memory and validate timestamps only when you need instant deploy reflection.

; /etc/php/8.5/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.jit=1255
opcache.jit_buffer_size=128M

With validate_timestamps=0, you must reload PHP-FPM after deploy. That is normal on servers I manage. See the official PHP OPcache documentation for JIT trade-offs on CPU-bound apps.

Technique 8: Move cache, sessions, and queues to Redis 8.10

File and database cache drivers work on small sites. They fall apart under concurrent traffic. Redis handles cache, sessions, and queues from one service.

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Run at least one dedicated queue worker via Supervisor. A backlog of unprocessed jobs eventually slows user-facing flows that dispatch them synchronously by mistake.

DriverBest forThroughputOps complexity
FileLocal dev, tiny sitesLowMinimal
DatabaseShared hosting without RedisMedium-lowLow
RedisProduction Laravel 13 appsHighModerate
Memcached 1.6.xCache-only, multi-serverHighModerate

Technique 9: Cache full HTTP responses for anonymous pages

Public blog posts, landing pages, and legal guides rarely change per visitor. Response caching skips controller work entirely.

/* routes/web.php */
Route::middleware('cache.headers:public;max_age=3600')->group(function () {
    Route::get('/guides/{slug}', [GuideController::class, 'show'])
        ->middleware('cacheResponse:3600');
});

Spatie's response cache package is a solid choice. Invalidate on model update events. Never cache authenticated dashboards without user-specific cache keys.

Technique 10: Offload slow tasks to queued jobs

Email, PDF generation, image processing, and third-party API calls do not belong in the request cycle. Dispatch a job and return a 202 or redirect.

GenerateInvoicePdf::dispatch($order)->onQueue('documents');

/* Job class */
public function handle(): void
{
    $pdf = Pdf::loadView('invoices.show', ['order' => $this->order]);
    Storage::put($this->path, $pdf->output());
}

On trek booking systems with supplier CRM flows, queue workers handle itinerary PDFs while the user sees a confirmation screen immediately.

Laravel Caching LayersLayer 1: OPcache (compiled PHP bytecode)Survives across requests in PHP-FPM workersLayer 2: Config / Route / View / Event cacheArtisan commands at deploy timeLayer 3: Redis application cache + sessionsQuery results, tagged invalidation, rate limitsLayer 4: HTTP response cache + CDN edgeFull page for anonymous traffic
Effective Laravel caching stacks OPcache, deploy-time artisan caches, Redis data cache, and HTTP-level response caching.

How do you optimize Laravel frontend assets and HTTP delivery?

Backend tuning means little if each page loads 2 MB of JavaScript and unoptimised images. These three techniques target the browser and the edge.

Technique 11: Ship a production Vite 8.x build with hashed filenames

Development assets are unminified and unbundled. Production must run npm run build and commit or deploy the public/build manifest.

/* vite.config.js — typical Laravel 13 setup */
export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: true,
        }),
    ],
    build: {
        rollupOptions: {
            output: { manualChunks: { vendor: ['axios', 'lodash'] } },
        },
    },
});

Enable Brotli or gzip at Nginx or Apache. Long cache headers on hashed assets are safe because the filename changes when content changes.

Technique 12: Optimise and lazy-load images in the media pipeline

Upload directories on legal-tech portals fill with scanned PDFs and high-resolution photos. Generate responsive variants at upload time. Lazy-load below-the-fold images in Blade.

<img
    class="lazyload img-fluid"
    data-src="{{ $media->getUrl('webp-large') }}"
    alt="{{ $article->title }}"
    width="800"
    height="450"
    loading="lazy"
>

Spatie Media Library with WebP conversions is a pattern I use regularly. Pair image work with sensible max upload limits in PHP and Nginx.

Technique 13: Put static assets and public pages behind a CDN

Serve /build/* and user uploads from a CDN subdomain. Enable HTTP/2 or HTTP/3 at the edge. Set Cache-Control headers explicitly in middleware or server config.

/* Nginx snippet for hashed Vite assets */
location /build/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    try_files $uri =404;
}

For Nepal-hosted apps with global visitors—florist eCommerce is a common case—CDN latency drops more than any PHP tweak. See our international WooCommerce and Laravel storefront work for multi-region delivery patterns.

What production server and Laravel configuration changes speed up apps in 2026?

The last five techniques live outside app/. They matter as much as Eloquent tuning on a live server under load.

Technique 14: Disable debug tooling and tighten PHP-FPM pools

Debugbar, Telescope, and Clockwork belong in local and staging only. Accidentally leaving them enabled leaks queries and doubles response time.

/* config/app.php — bind via .env */
'debug' => (bool) env('APP_DEBUG', false),

/* composer.json — move debugbar to require-dev */
"require-dev": {
    "barryvdh/laravel-debugbar": "^3.14"
}

Size PHP-FPM pools to RAM. A 2 GB VPS with 50 max children will swap and die. Start with pm.max_children = 10 on small instances and watch memory per worker with ps or Pulse.

Technique 15: Consider Laravel Octane for high-concurrency APIs

Octane keeps the application booted in memory using Swoole or RoadRunner. It suits stateless API endpoints with careful memory management. It is not a default choice for every CRUD app.

composer require laravel/octane
php artisan octane:install --server=swoole
php artisan octane:start --workers=4 --max-requests=500

Avoid storing request-specific state in singletons. Reset connections between requests. Read the Laravel Octane documentation before enabling on a document-upload portal with large in-memory buffers.

Bonus checks that prevent regressions after deploy

These are not separate headline techniques but they save hours in production debugging.

  1. Run composer install --no-dev --optimize-autoloader on deploy with Composer 2.10.
  2. Set APP_ENV=production and LOG_LEVEL=warning to cut disk I/O from verbose logs.
  3. Use database connection pooling or read replicas only when metrics prove write contention—not prematurely.
  4. Schedule php artisan queue:restart after deploy so workers load new code.
  5. Monitor queue depth, slow queries, and 502 rates with Laravel Pulse or an external APM.

Validate JSON API payloads during development with our JSON formatter tool before load tests hit malformed responses. For regex-heavy validation rules, the regex tester catches catastrophic backtracking early.

Deploy Pipeline: Performance StepsGit pushGitLab CIComposer--no-devArtisancache *Vite buildnpm run buildFPMreloadPost-deploy verificationTTFB, query count, queue depth, OPcache hit rateCompare against baseline — rollback if regression
Laravel performance optimization belongs in the deploy pipeline: Composer optimisations, artisan caches, Vite builds, and PHP-FPM reload with post-deploy checks.

On sister sites sharing one EC2 host—notary portals and translation services among them—stale cron paths and missed FPM reloads cause more post-deploy slowness than bad algorithms. Treat performance steps as part of CI, not a manual checklist.

If you need a structured audit before a busy season—Dashain eCommerce spikes are predictable—testing and optimization services should cover load profiles, not just Lighthouse scores. Budget roughly Rs 25,000–80,000 (~USD 185–590) for a focused Laravel audit in Nepal, depending on app size and staging access.

For ongoing work after launch, support and maintenance retainers catch query regressions when new features land. API-heavy integrations need separate load testing because Octane and queue tuning interact with webhook throughput.

Founders comparing build options can review Laravel legal-tech portals in the portfolio and client portal implementations that balance document uploads with page speed. Customer reviews mention on-time delivery; performance was part of that because slow admin panels block staff daily.

Read more engineering notes on the blog. Learn about the author on the about page or the homepage. Custom Laravel builds start from custom software development and web development services.

Key Takeaways

  • Measure query count and TTFB before changing anything—optimization without baselines is guesswork.
  • Fix N+1 queries, add matching indexes, and cache expensive aggregates before chasing exotic tools.
  • Run config:cache, route:cache, and view:cache on every production deploy, then reload PHP-FPM.
  • Move cache, sessions, and queues to Redis; keep slow IO in background jobs.
  • Build frontend assets with Vite, optimise images, and serve static files through a CDN with long cache headers.
  • Disable debug packages in production and size PHP-FPM pools to available RAM on the server.

People Also Ask

How much faster can Laravel performance optimization make my app?

Results vary by starting point. A site with N+1 queries, no route cache, and file-based sessions often sees 40–70% TTFB improvement from the first six techniques alone. Apps already on Redis with cached routes may gain only 10–15% unless database indexes or asset delivery are the bottleneck.

Is Redis required for Laravel performance optimization in 2026?

Not strictly required, but strongly recommended for any app with more than a few concurrent users or background jobs. Redis 8.10 handles cache, sessions, and queues from one service. Database and file drivers become contention points under load.

Should I use Laravel Octane for every project?

No. Octane fits high-throughput, mostly stateless APIs when you understand memory lifecycle and connection reset rules. Traditional PHP-FPM plus good caching is the right default for admin-heavy CRUD apps and document upload portals.

When should I hire someone for Laravel performance optimization?

Bring in help when slow queries persist after eager loading and indexes, when deploys cause downtime, or when you lack staging that mirrors production traffic. A senior engineer can usually identify the top three bottlenecks in a day if logs and database access are available.

Apply these 15 techniques and measure every change

Laravel performance optimization: 15 Techniques That Work is a checklist, not a one-time sprint. Cache on deploy, watch queries in staging, queue anything slower than 200 ms, and keep OPcache and PHP-FPM aligned with your server RAM. The wins compound when the whole stack—not just one controller—gets attention.

Need a production audit, deploy pipeline fix, or Redis migration on Laravel 12 or 13? Contact us with your stack details and current TTFB numbers. We will tell you honestly which of the 15 techniques matter most for your app.

Frequently Asked Questions

A layered stack of measurable changes across PHP, the framework bootstrap, database queries, cache, queues, web server, and frontend assets—not one magic switch.

The article groups them into database (fix N+1 with eager loading, add matching indexes, narrow SELECT lists, chunk large exports, cache tagged aggregates), caching (deploy-time artisan caches, OPcache tuning, Redis for cache/sessions/queues, HTTP response caching, queued jobs), frontend (Vite 8.x production builds, image lazy-loading, CDN delivery), and server config (disable debug tools, size PHP-FPM pools, optional Laravel Octane). Each targets a real bottleneck seen after launch when data and traffic grow.

Start with a baseline: record TTFB, query count, and memory before changing config or code. Locally, install barryvdh/laravel-debugbar and watch the query tab on list pages—anything above 10–15 queries on a simple index deserves investigation. In staging, Laravel Pulse helps; DB::listen() logging works for raw query counts. Without measurement, you cannot prove a change helped, and post-upgrade regressions on Laravel 13.x with PHP 8.3 or 8.5 are easy to miss.

The classic N+1 loads related models inside a loop, turning one query into hundreds. Use with() for relationships you know you need on list pages—for example Booking::with(['customer', 'tour'])->latest()->paginate(20) instead of loading customer inside foreach. Eager loading often cuts dozens of duplicate queries down to two. Pair this with barryvdh/laravel-debugbar in local only and re-check after every major framework upgrade.

Missing indexes show up once tables pass a few hundred thousand rows. Check slow query logs on MySQL 8.4 LTS or MySQL 9.7, then add composite indexes for filter combinations you use often—such as status plus created_at on bookings, plus foreign keys like customer_id. Run EXPLAIN before and after. Do not index every column; each index slows writes. Index only columns that appear in real WHERE and JOIN clauses your Eloquent queries generate.

Narrow SELECT lists on list views instead of SELECT *, including foreign keys needed for eager loading—for example customer:id,name alongside booking id, reference, status, created_at. For exports and reports, never call Model::all(); use chunkById() or lazy() and stream output, ideally from a queued job so the HTTP request returns immediately. Cache expensive dashboard aggregates with Redis tagged cache and flush tags when underlying records change so totals stay trustworthy.

Run php artisan config:cache, route:cache, view:cache, and event:cache after composer install on deploy. Laravel compiles routes, config, views, and events into flat files; skipping this leaves the framework parsing PHP arrays on every request. Add these to your Deployer 7 recipe, then reload PHP-FPM so OPcache picks up changed files. Never run config:cache if config closures depend on runtime env() calls outside config files—that breaks production silently.

Default Ubuntu OPcache settings are conservative. Raise opcache.memory_consumption to 256, opcache.max_accelerated_files to 20000, enable opcache.jit with opcache.jit_buffer_size 128M for CPU-bound workloads, and set opcache.validate_timestamps=0 in production. With validate_timestamps disabled, you must reload PHP-FPM after every deploy—normal on servers managed with Deployer and GitLab CI. OPcache keeps compiled bytecode in memory and is one of the fastest wins after deploy-time artisan caches.

File and database cache drivers work on small sites but fall apart under concurrent traffic. For production Laravel 13 apps, move CACHE_STORE, SESSION_DRIVER, and QUEUE_CONNECTION to redis with REDIS_CLIENT=phpredis. Redis 8.10 handles all three from one service with high throughput. Run at least one dedicated queue worker via Supervisor; unprocessed job backlogs eventually slow user-facing flows. Memcached 1.6.x remains a cache-only alternative for multi-server setups but does not replace session and queue needs.

Public blog posts, landing pages, and legal guides rarely change per visitor. Use response caching middleware—for example cache.headers plus Spatie's response cache package on anonymous routes with a TTL like 3600 seconds. This skips controller work entirely. Invalidate on model update events. Never cache authenticated dashboards without user-specific cache keys; shared cache keys leak one user's data to another and create both performance and security problems.

Email, PDF generation, image processing, and third-party API calls do not belong in the request cycle. Dispatch a job—such as GenerateInvoicePdf::dispatch($order)->onQueue('documents')—and return a 202 or redirect immediately. On booking systems with supplier CRM flows, queue workers handle itinerary PDFs while the user sees a confirmation screen. Schedule php artisan queue:restart after deploy so workers load new code, and monitor queue depth with Laravel Pulse or an external APM.

Development assets are unminified and unbundled. Production must run npm run build and deploy the public/build manifest with hashed filenames from Vite 8.x. Split vendor chunks for libraries like axios and lodash in vite.config.js. Enable Brotli or gzip at Nginx or Apache, and set long Cache-Control headers on hashed assets because filenames change when content changes. Backend tuning means little if each page still loads 2 MB of unoptimised JavaScript.

Budget roughly Rs 25,000–80,000 (~USD 185–590) for a focused audit, depending on app size and staging access.

Accidentally leaving Debugbar, Telescope, or Clockwork enabled in production leaks queries and doubles response time—keep barryvdh/laravel-debugbar in require-dev and APP_DEBUG=false. Size PHP-FPM pools to RAM; a 2 GB VPS with pm.max_children=50 will swap and die—start around 10 on small instances and watch memory per worker. Run composer install --no-dev --optimize-autoloader with Composer 2.10, set LOG_LEVEL=warning, and reload PHP-FPM after deploy. Stale cron paths and missed FPM reloads cause more post-deploy slowness than bad algorithms.

Use Octane for stateless, high-concurrency API endpoints—not as the default for every CRUD app.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: