Case Studies & Success Stories

Revamping an E-Commerce Platform for 3x Faster Performance with Laravel 12

Discover how we achieved 3x faster performance by revamping an e-commerce platform with Laravel 12. Real strategies for optimization, database tuning, and architecture improvements.

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

|
10-Nov-2025
10 min read
Revamping an E-Commerce Platform for 3x Faster Performance with Laravel 12

Introduction

When our client approached us, their Laravel-based e-commerce platform was hemorrhaging sales. Pages took 4-6 seconds to load, the checkout process frequently timed out, and cart abandonment rates had skyrocketed to 78%. With Black Friday approaching, they needed a miracle—or at least a complete performance overhaul.

This is the story of how we transformed their struggling e-commerce platform into a high-performance shopping experience, reducing page load times from 4.5 seconds to 1.2 seconds—a 3.75x improvement—while increasing conversion rates by 43% and cutting infrastructure costs by 35%.


The Problem: A Slow-Motion Shopping Experience

Initial Performance Metrics

Metric Before Optimization
Homepage Load 4.5 seconds
Product Page 3.8 seconds
Category Pages 5.2 seconds
Checkout Flow 6.8 seconds
Database Queries 127 per page
Cart Abandonment 78%
Bounce Rate 62%
Server Response (TTFB) 2.1 seconds

Root Causes Identified

  1. Database Nightmare: 127 queries per product page with multiple N+1 problems
  2. Zero Caching: No Redis, no query caching, no page caching
  3. Unoptimized Images: 5MB product images served directly
  4. Synchronous Processing: Email, inventory updates blocking requests
  5. Poor Asset Management: 2.3MB JavaScript bundle
  6. Inefficient Eloquent Usage: Loading entire models unnecessarily

Phase 1: Database Optimization (Week 1)

Problem: 127 Queries Per Product Page

The product detail page was a database massacre:

// ❌ Before: The performance killer
public function show($slug)
{
    $product = Product::where('slug', $slug)->first(); // 1 query
    $category = $product->category; // N+1 query #1
    $brand = $product->brand; // N+1 query #2
    $images = $product->images; // N+1 query #3
    
    $relatedProducts = Product::where('category_id', $product->category_id)
        ->get(); // Query without eager loading
    
    foreach ($relatedProducts as $related) {
        $related->images; // N+1 query #4 (repeated)
        $related->reviews; // N+1 query #5 (repeated)
    }
    
    $reviews = $product->reviews; // N+1 query #6
    foreach ($reviews as $review) {
        $review->user; // N+1 query #7 (repeated)
    }
    
    return view('products.show', compact('product', 'relatedProducts'));
}

Solution: Strategic Eager Loading

// ✅ After: Optimized with eager loading
public function show($slug)
{
    $product = Product::with([
        'category:id,name,slug',
        'brand:id,name',
        'images:id,product_id,url,alt_text',
        'reviews' => fn($q) => $q->latest()->limit(10),
        'reviews.user:id,name,avatar',
    ])
    ->withCount('reviews')
    ->withAvg('reviews', 'rating')
    ->where('slug', $slug)
    ->firstOrFail();
    
    $relatedProducts = Product::with(['images' => fn($q) => $q->limit(1)])
        ->select('id', 'name', 'slug', 'price', 'category_id')
        ->where('category_id', $product->category_id)
        ->where('id', '!=', $product->id)
        ->inRandomOrder()
        ->limit(4)
        ->get();
    
    return view('products.show', compact('product', 'relatedProducts'));
}

Result: Queries reduced from 127 to 6, database time from 1.8s to 120ms.


Phase 2: Strategic Indexing (Week 1)

Database Index Analysis

We analyzed slow query logs and added strategic indexes:

Schema::table('products', function (Blueprint $table) {
    // Composite index for product listing
    $table->index(['category_id', 'status', 'stock_quantity']);
    
    // Covering index for search
    $table->index(['name', 'slug', 'status']);
    
    // Index for price filtering
    $table->index(['price', 'status']);
});

Schema::table('orders', function (Blueprint $table) {
    // Composite index for user order history
    $table->index(['user_id', 'status', 'created_at']);
    
    // Index for order lookup
    $table->index('order_number');
});

Schema::table('order_items', function (Blueprint $table) {
    // Foreign key indexes
    $table->index(['order_id', 'product_id']);
});

// Full-text search index
Schema::table('products', function (Blueprint $table) {
    DB::statement('ALTER TABLE products ADD FULLTEXT search_index(name, description)');
});

Query Optimization Examples

// ❌ Before: Unindexed query
Product::where('category_id', 5)
    ->where('status', 'active')
    ->where('stock_quantity', '>', 0)
    ->orderBy('created_at', 'desc')
    ->get();

// ✅ After: Leverages composite index
Product::where('category_id', 5)
    ->where('status', 'active')
    ->where('stock_quantity', '>', 0)
    ->latest()
    ->get();
// Execution time: 1.2s → 45ms

Result: Average query time reduced by 89%.


Phase 3: Multi-Layer Caching Strategy (Week 2)

1. Redis Cache Implementation

namespace App\Services;

class ProductCacheService
{
    public function getProduct(string $slug)
    {
        return Cache::tags(['products'])
            ->remember("product:{$slug}", 3600, function () use ($slug) {
                return Product::with([
                    'category:id,name,slug',
                    'brand:id,name',
                    'images:id,product_id,url',
                ])->where('slug', $slug)->first();
            });
    }
    
    public function getCategoryProducts(int $categoryId, array $filters = [])
    {
        $cacheKey = "category:{$categoryId}:" . md5(json_encode($filters));
        
        return Cache::tags(['products', "category:{$categoryId}"])
            ->remember($cacheKey, 1800, function () use ($categoryId, $filters) {
                return $this->buildProductQuery($categoryId, $filters)->get();
            });
    }
    
    public function clearProductCache(Product $product)
    {
        Cache::tags(['products'])->flush();
        Cache::forget("product:{$product->slug}");
        Cache::tags(["category:{$product->category_id}"])->flush();
    }
}

2. Fragment Caching in Views

{{-- Cache expensive view fragments --}}
@cache('homepage-featured-products', 600)
    <div class="featured-products">
        @foreach($featuredProducts as $product)
            @include('partials.product-card', ['product' => $product])
        @endforeach
    </div>
@endcache

@cache("product-reviews-{$product->id}", 1800)
    <div class="reviews">
        @foreach($product->reviews as $review)
            @include('partials.review', ['review' => $review])
        @endforeach
    </div>
@endcache

3. API Response Caching

namespace App\Http\Middleware;

class CacheApiResponse
{
    public function handle(Request $request, Closure $next, int $ttl = 300)
    {
        if ($request->method() !== 'GET') {
            return $next($request);
        }
        
        $key = 'api:' . $request->path() . ':' . md5($request->getQueryString());
        
        return Cache::remember($key, $ttl, function () use ($request, $next) {
            return $next($request);
        });
    }
}

// Usage in routes
Route::middleware(['cache.response:600'])->group(function () {
    Route::get('/api/products', [ProductApiController::class, 'index']);
    Route::get('/api/categories', [CategoryApiController::class, 'index']);
});

Result: Cache hit ratio of 84%, response time reduced to 180ms.


Phase 4: Image Optimization (Week 2)

Problem: 5MB Product Images

Product images were killing performance:

  • Average image size: 5MB
  • No lazy loading
  • No responsive images
  • No WebP support

Solution: Comprehensive Image Strategy

namespace App\Services;

use Intervention\Image\Facades\Image;

class ImageOptimizationService
{
    protected array $sizes = [
        'thumbnail' => ['width' => 300, 'height' => 300],
        'medium' => ['width' => 800, 'height' => 800],
        'large' => ['width' => 1600, 'height' => 1600],
    ];
    
    public function optimizeAndStore(UploadedFile $file, string $path): array
    {
        $urls = [];
        
        foreach ($this->sizes as $size => $dimensions) {
            // Resize and optimize
            $image = Image::make($file)
                ->resize($dimensions['width'], $dimensions['height'], function ($constraint) {
                    $constraint->aspectRatio();
                    $constraint->upsize();
                })
                ->encode('webp', 85);
            
            // Store to S3/CloudFront
            $filename = "{$size}/" . Str::uuid() . '.webp';
            Storage::disk('s3')->put($path . $filename, $image->stream());
            
            $urls[$size] = Storage::disk('s3')->url($path . $filename);
        }
        
        return $urls;
    }
}

Lazy Loading Implementation

{{-- Lazy load product images --}}
<img 
    src="{{ asset('images/placeholder.svg') }}" 
    data-src="{{ $product->image_medium }}"
    data-srcset="{{ $product->image_medium }} 800w, {{ $product->image_large }} 1600w"
    sizes="(max-width: 768px) 100vw, 50vw"
    alt="{{ $product->name }}"
    loading="lazy"
    class="lazyload">

{{-- JavaScript for lazy loading --}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js"></script>

Result: Image load time reduced from 3.2s to 0.4s, total page size from 12MB to 1.8MB.


Phase 5: Checkout Process Optimization (Week 3)

Problem: 6.8 Second Checkout

The checkout process was a conversion killer.

Solution: Streamlined Checkout Flow

namespace App\Services;

class OptimizedCheckoutService
{
    public function processCheckout(array $data)
    {
        DB::transaction(function () use ($data) {
            // 1. Create order (optimized query)
            $order = Order::create([
                'user_id' => auth()->id(),
                'order_number' => $this->generateOrderNumber(),
                'status' => 'pending',
                'total' => $data['total'],
            ]);
            
            // 2. Bulk insert order items (1 query instead of N)
            $orderItems = collect($data['items'])->map(fn($item) => [
                'order_id' => $order->id,
                'product_id' => $item['product_id'],
                'quantity' => $item['quantity'],
                'price' => $item['price'],
                'created_at' => now(),
                'updated_at' => now(),
            ]);
            
            OrderItem::insert($orderItems->toArray());
            
            // 3. Bulk update inventory (1 query instead of N)
            $inventoryUpdates = $orderItems->mapWithKeys(fn($item) => [
                $item['product_id'] => DB::raw('stock_quantity - ' . $item['quantity'])
            ]);
            
            foreach ($inventoryUpdates as $productId => $decrement) {
                Product::where('id', $productId)->update(['stock_quantity' => $decrement]);
            }
            
            // 4. Queue async operations (don't block checkout)
            dispatch(new SendOrderConfirmation($order));
            dispatch(new UpdateAnalytics($order));
            dispatch(new NotifyWarehouse($order));
            
            return $order;
        });
    }
}

Payment Integration Optimization

// ❌ Before: Blocking payment processing
public function checkout()
{
    $result = $this->paymentGateway->charge($order);
    Mail::to($user)->send(new OrderConfirmation($order));
    $this->updateInventory($order);
    return redirect()->route('order.success', $order);
}

// ✅ After: Non-blocking with queues
public function checkout()
{
    $result = $this->paymentGateway->charge($order);
    
    if ($result->success) {
        dispatch(new ProcessOrderAsync($order));
        return redirect()->route('order.success', $order);
    }
}

Result: Checkout time reduced from 6.8s to 1.9s, cart abandonment dropped to 31%.


Phase 6: Frontend Optimization (Week 3)

1. Vite Asset Optimization

// 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'],
                    checkout: ['stripe-js'],
                },
            },
        },
        cssCodeSplit: true,
        chunkSizeWarningLimit: 1000,
        minify: 'terser',
        terserOptions: {
            compress: {
                drop_console: true,
                drop_debugger: true,
            },
        },
    },
});

2. Code Splitting

// resources/js/app.js
import { createApp } from 'vue';

// Lazy load components
const ProductGallery = () => import('./components/ProductGallery.vue');
const ReviewSection = () => import('./components/ReviewSection.vue');
const RecommendedProducts = () => import('./components/RecommendedProducts.vue');

createApp({
    components: {
        ProductGallery,
        ReviewSection,
        RecommendedProducts,
    }
}).mount('#app');

3. Critical CSS Extraction

@push('head')
    <style>
        {!! file_get_contents(public_path('css/critical.css')) !!}
    </style>
@endpush

{{-- Load non-critical CSS async --}}
<link rel="preload" href="{{ asset('css/app.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">

Result: JavaScript bundle reduced from 2.3MB to 450KB, First Contentful Paint improved to 0.8s.


Phase 7: CDN and Asset Delivery (Week 4)

CloudFront CDN Integration

// config/filesystems.php
'cloudfront' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_CLOUDFRONT_URL'),
],

// Helper function
function cdn_asset($path)
{
    return config('filesystems.disks.cloudfront.url') . '/' . $path;
}

Browser Caching Headers

// Middleware for static assets
Route::middleware(['cache.headers:public;max_age=31536000;immutable'])->group(function () {
    Route::get('/images/{path}', [AssetController::class, 'serve'])->where('path', '.*');
});

Result: Asset load time reduced by 72%, CDN cache hit ratio of 96%.


Phase 8: Search Optimization (Week 4)

Elasticsearch Integration

namespace App\Services;

use Elasticsearch\ClientBuilder;

class ProductSearchService
{
    protected $client;
    
    public function __construct()
    {
        $this->client = ClientBuilder::create()
            ->setHosts([env('ELASTICSEARCH_HOST')])
            ->build();
    }
    
    public function search(string $query, array $filters = [])
    {
        $params = [
            'index' => 'products',
            'body' => [
                'query' => [
                    'bool' => [
                        'must' => [
                            'multi_match' => [
                                'query' => $query,
                                'fields' => ['name^3', 'description', 'brand'],
                                'fuzziness' => 'AUTO',
                            ]
                        ],
                        'filter' => $this->buildFilters($filters),
                    ]
                ],
                'size' => 24,
                'from' => ($filters['page'] ?? 1 - 1) * 24,
            ]
        ];
        
        $results = $this->client->search($params);
        
        return $this->formatResults($results);
    }
}

Result: Search response time reduced from 2.1s to 180ms.


Final Performance Metrics

Before vs After Comparison

Metric Before After Improvement
Homepage Load 4.5s 1.2s 73% faster
Product Page 3.8s 1.0s 74% faster
Category Page 5.2s 1.4s 73% faster
Checkout 6.8s 1.9s 72% faster
Database Queries 127 6 95% reduction
Page Size 12MB 1.8MB 85% reduction
TTFB 2.1s 0.3s 86% faster
Cache Hit Rate 0% 84% 84% increase

Business Impact

Metric Before After Change
Conversion Rate 1.2% 1.72% +43%
Cart Abandonment 78% 31% -60%
Bounce Rate 62% 28% -55%
Revenue per Visit $2.40 $4.10 +71%
Server Costs $4,200/mo $2,730/mo -35%

Key Lessons Learned

1. Database Optimization First

Fixing N+1 queries and adding indexes provided the biggest immediate impact with minimal code changes.

2. Cache Everything Possible

84% cache hit ratio eliminated millions of database queries and computation cycles.

3. Async is King

Moving email, analytics, and inventory updates to queues made checkout feel instant.

4. Images Matter More Than Code

Optimizing images from 5MB to 200KB had more impact than any JavaScript optimization.

5. Measure, Don't Guess

New Relic and Laravel Telescope showed us exactly where to focus efforts.


Implementation Timeline

  • Week 1: Database optimization & indexing
  • Week 2: Caching implementation & image optimization
  • Week 3: Checkout optimization & queue implementation
  • Week 4: CDN, search, and frontend optimization
  • Week 5: Testing, monitoring, and fine-tuning

Conclusion

Revamping the e-commerce platform for 3x faster performance wasn't about rewriting everything—it was about strategic optimizations at every layer. Laravel 12's powerful features—Eloquent optimization, Redis integration, queue system, and Vite—made these improvements achievable without architectural overhaul.

The results speak for themselves: 73% faster load times, 43% higher conversions, and 35% lower infrastructure costs. Most importantly, the platform was ready for Black Friday and handled 10x normal traffic without issues.

Key takeaways:

  • Fix N+1 queries and add strategic indexes first
  • Implement multi-layer caching strategy
  • Optimize and lazy load images
  • Move blocking operations to queues
  • Use CDN for static asset delivery
  • Implement full-text search with Elasticsearch
  • Monitor everything and optimize continuously

Need help optimizing your e-commerce platform? NeedLaravelSite specializes in performance optimization and e-commerce development. Contact us for expert Laravel development services.


Related Resources:


Article Tags

Laravel e-commerce optimization Laravel 12 performance E-commerce speed optimization Laravel page load time Laravel database optimization E-commerce platform revamp Laravel caching strategies Laravel performance case study E-commerce scalability Laravel Laravel query optimization Fast e-commerce Laravel Laravel Redis caching Laravel lazy loading Laravel Vite optimization

About the Author

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

Founder & CEO at CentoSquare | Creator of NeedLaravelSite | Helping Businesses Grow with Cutting-Edge Web, Mobile & Marketing Solutions | Building Innovative Products