The Problem
A client came to us with a serious problem: their Laravel e-commerce website was taking 7-9 seconds to load on a standard broadband connection. Their bounce rate was over 70%, and their Google PageSpeed score was a dismal 18/100 on mobile.
They were losing customers every single day. Here's exactly how we fixed it.
Step 1: Diagnosis
Before touching a single line of code, we ran a thorough diagnosis using:
- Google PageSpeed Insights — Identified LCP, FID, and CLS issues
- WebPageTest — Waterfall analysis showing which resources were blocking render
- Laravel Debugbar — Revealed 847 database queries on the homepage alone
- Chrome DevTools Network tab — Found 4.2MB of uncompressed images
The culprits were clear: N+1 query problems, massive unoptimized images, no caching, and a 2.1MB JavaScript bundle loaded synchronously.
Step 2: Fixing the N+1 Query Problem
The homepage was loading products, then for each product loading its category, brand, and reviews separately. 847 queries became 6 with eager loading:
// Before: 847 queries
$products = Product::all();
// In the view: $product->category->name (N+1!)
// After: 6 queries
$products = Product::with(['category', 'brand', 'reviews' => fn($q) => $q->latest()->limit(3)])
->where('is_active', true)
->paginate(24);
This single change reduced server response time from 4.2s to 0.8s.
Step 3: Image Optimization
The site had product images up to 4000×4000px being displayed at 300×300px. We:
- Converted all images to WebP format (average 65% size reduction)
- Implemented responsive images with srcset
- Added lazy loading to all below-the-fold images
- Set up automatic image resizing on upload using Laravel's Intervention Image
Total image payload reduced from 4.2MB to 380KB.
Step 4: Caching Strategy
We implemented a multi-layer caching strategy:
- Route caching —
php artisan route:cachereduced routing overhead - Config caching —
php artisan config:cache - View caching — Compiled Blade templates cached
- Redis query caching — Homepage data cached for 5 minutes
- HTTP caching headers — Static assets cached for 1 year in browser
Step 5: JavaScript and CSS Optimization
The 2.1MB JavaScript bundle was loaded synchronously in the <head>, blocking all rendering. We:
- Moved scripts to the bottom of
<body>withdefer - Code-split the bundle — only load what's needed per page
- Removed 14 unused jQuery plugins (legacy code)
- Minified and compressed all CSS and JS
- Inlined critical CSS to eliminate render-blocking stylesheets
JavaScript bundle: 2.1MB → 340KB. CSS: 890KB → 78KB.
Step 6: Server Configuration
- Enabled Gzip/Brotli compression on Nginx
- Configured PHP OPcache for bytecode caching
- Set up a CDN (Cloudflare) for static asset delivery
- Upgraded PHP from 7.4 to 8.3 (significant performance improvement)
The Results
| Metric | Before | After |
|---|---|---|
| Page Load Time | 8.2s | 0.87s |
| Database Queries | 847 | 6 |
| Page Size | 7.1MB | 820KB |
| PageSpeed (Mobile) | 18 | 91 |
| Bounce Rate | 71% | 34% |
| Conversion Rate | 0.8% | 2.3% |
Key Takeaways
- Always profile before optimizing — you can't fix what you can't measure
- Database query optimization usually has the highest ROI
- Images are almost always the biggest performance culprit
- Caching is not optional for production applications
- Every 100ms of load time improvement measurably impacts conversion rates
Is your website slow? We offer free performance audits. Get in touch and let's see what we can do.