Image Optimization
Server-side image processing for optimized delivery
Overview
The ImageService uses Sharp to process uploaded images into optimized variants before storage. This reduces bandwidth usage and improves load times across the platform.
Image Variants
| Variant | Size | Format | Use Case |
|---|---|---|---|
| Thumbnail | 150×150 | WebP | List views, grids, cards |
| Medium | 600px width | WebP | Detail pages, modals |
| Original | Preserved | WebP | Full-size viewing |
All variants are converted to WebP for optimal compression while maintaining quality.
Usage
Processing Images on Upload
import { ImageService } from './modules/image/image.service';
@Injectable()
export class YourService {
constructor(private imageService: ImageService) {}
async uploadWithVariants(file: Express.Multer.File) {
// Check if file is an image
if (!this.imageService.isProcessableImage(file.mimetype)) {
// Not an image - proceed with regular upload
return;
}
// Process into variants
const variants = await this.imageService.processImageVariants(
file.buffer,
file.mimetype
);
// Upload each variant to storage
// variants.thumbnail, variants.medium, variants.original
}
}Generating Blur Placeholders
// Generate a base64 blur placeholder for progressive loading
const placeholder = await imageService.generateBlurPlaceholder(buffer);
// Returns: "data:image/webp;base64,..."API Reference
isProcessableImage(mimetype: string): boolean
Returns true if the mimetype is a supported image format.
Supported formats:
image/jpeg,image/jpgimage/pngimage/webpimage/gifimage/avif
processImageVariants(buffer, mimetype): Promise<ImageVariants>
Processes an image buffer into thumbnail, medium, and optimized original.
Returns:
interface ImageVariants {
thumbnail: ProcessedImage; // 150x150 cropped
medium: ProcessedImage; // 600px width
original: ProcessedImage; // Optimized original
}
interface ProcessedImage {
buffer: Buffer;
mimetype: string; // 'image/webp'
size: number;
}generateBlurPlaceholder(buffer): Promise<string>
Creates a tiny 20×20 blurred preview as base64 data URI.
Cache Headers
All uploaded images receive these cache headers:
Cache-Control: public, max-age=31536000, immutableThis instructs browsers and CDNs to cache images for 1 year. Since image URLs include unique keys, content changes result in new URLs.
Integration Points
File Schema
The File model includes fields for variant URLs:
{
url: string; // Original image URL
thumbnailUrl?: string;
mediumUrl?: string;
}Frontend Usage
Use appropriate variant based on context:
// Grid view - use thumbnail
<img src={artist.thumbnailUrl || artist.image} />
// Detail page - use medium
<img src={artist.mediumUrl || artist.image} />Performance Benchmarks
| Format | Original | WebP Thumbnail | WebP Medium | Savings |
|---|---|---|---|---|
| JPEG (2MB) | 2048 KB | ~5 KB | ~40 KB | 98% |
| PNG (4MB) | 4096 KB | ~6 KB | ~50 KB | 99% |
Dependencies
npm install sharp
npm install --save-dev @types/sharpLast Updated: February 2026