Thumbnail Generation in Multiple Sizes
Uploaded images need to appear in different contexts — product card, article preview, avatar, OG tag. Storing the original and resizing it on every request is a bad idea: CPU goes to rendering, latency grows, LCP suffers. The right approach is to generate a set of fixed sizes at upload time and serve static files.
We specialize in image optimization. Our approach is turnkey thumbnail generation using proven tools. Below we explain how to implement this in Laravel/PHP or Node.js/sharp. We guarantee your pages load fast and pass Core Web Vitals.
Typical Image Handling Problems
Without a thumbnail generation system, errors like 'hydrate-mismatch' due to different sizes on client and server, high LCP from loading originals, and wasted traffic occur. We solve these with a predefined size config and conversion to WebP.
Eager vs Lazy Generation
Eager generation — all sizes created immediately upon file upload. Suitable when the size set is stable. Simpler serving logic, no cache misses.
Lazy generation — size generated on first request, then cached. Suitable for dynamic projects where needed sizes are unknown upfront.
| Characteristic | Eager generation | Lazy generation |
|---|---|---|
| Upload load | High (multiple generations at once) | Low (only save original) |
| First request load | Low (file ready) | Medium (generate + cache) |
| Config management | Requires regeneration on change | Easy to change config |
| Implementation complexity | Low | Medium (needs cache + queue) |
For most projects, eager generation with a fixed config is sufficient.
Technology Stack
For PHP/Laravel — Intervention Image library on GD or Imagick. Imagick is preferred: better EXIF handling, wider format support, more accurate color profiles.
composer require intervention/image // config/image.php return [ 'driver' => 'imagick', // or 'gd' ]; For Node.js — sharp (libvips under the hood), the fastest option available:
npm install sharp Comparison: Intervention Image vs sharp
| Characteristic | Intervention Image | sharp |
|---|---|---|
| Platform | PHP | Node.js |
| Driver | GD / Imagick | libvips |
| Speed | Medium | Very high |
| Format support | JPEG, PNG, WebP, etc. | JPEG, PNG, WebP, AVIF, etc. |
| API simplicity | High | Medium |
| Memory usage | Higher | Lower |
Why Generate Thumbnails in WebP Immediately?
WebP is supported by all modern browsers — Chrome, Firefox, Safari (since 2020). It gives 25–35% weight savings over JPEG at the same visual quality. For OG tags and product cards, this reduces LCP by 10–20%. According to Google Web Fundamentals, replacing JPEG with WebP cuts load time by 30% on average.
Size Configuration
Store sizes in a separate config file, not scattered across code:
// config/thumbnails.php return [ 'sizes' => [ 'thumb' => ['width' => 150, 'height' => 150, 'fit' => 'crop'], 'small' => ['width' => 320, 'height' => null, 'fit' => 'width'], 'medium' => ['width' => 640, 'height' => null, 'fit' => 'width'], 'large' => ['width' => 1280, 'height' => null, 'fit' => 'width'], 'og' => ['width' => 1200, 'height' => 630, 'fit' => 'crop'], ], ]; fit: crop — center-crops while maintaining proportions. fit: width — scales by width, height recalculated.
Generation Service
We implement a service that creates all variants based on config:
namespace App\Services; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Intervention\Image\Facades\Image; class ThumbnailService { public function generateAll(UploadedFile $file, string $basePath): array { $original = Image::make($file); $sizes = config('thumbnails.sizes'); $paths = []; // Save original $ext = $file->getClientOriginalExtension(); $hash = sha1_file($file->getRealPath()); $originalPath = "{$basePath}/{$hash}.{$ext}"; Storage::disk('public')->put($originalPath, (string) $original->encode()); $paths['original'] = $originalPath; foreach ($sizes as $name => $params) { $img = clone $original; if ($params['fit'] === 'crop') { $img->fit($params['width'], $params['height'], function ($constraint) { $constraint->upsize(); }); } else { $img->resize($params['width'], $params['height'], function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }); } $sizePath = "{$basePath}/{$hash}_{$name}.webp"; Storage::disk('public')->put($sizePath, (string) $img->encode('webp', 85)); $paths[$name] = $sizePath; } return $paths; } } We generate directly in WebP — quality 85 gives a good size/quality balance.
Integration with Model and Controller
In the Media model, store paths to all variants in a JSON variants field. In the upload controller, call the service and save the result. Contact us for the full code listing.
Handling Config Changes
When the size set changes, old files must be regenerated. We create an artisan command with chunked processing (100 records per chunk) that iterates over all media files and recreates thumbnails. The command runs once and can be automated.
Example regeneration command
// Artisan command public function handle() { Media::chunk(100, function ($mediaItems) { foreach ($mediaItems as $media) { $this->thumbnailService->regenerate($media); } }); } What's Included in the Work
- Development of a size config tailored to your project
- Implementation of the generation service (eager or lazy)
- Integration with model and upload controller
- Queue setup for background generation (optional)
- Regeneration of existing thumbnails when config changes
- Documentation and repository access handover
Our Expertise
Over 5 years of web development experience, 100+ successful image optimization projects. We are Laravel and Node.js certified. Quality and deadlines guaranteed.
Estimated Timelines
Library setup, size config, generation service — 4–6 hours. Integration with model, upload handler, regeneration command — another 3–4 hours. If a queue (background Job) is needed — plus 2 hours.
Order a turnkey implementation: contact us for a project assessment. We'll prepare a custom quote with no hidden fees.







