Skip to content

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

VariantSizeFormatUse Case
Thumbnail150×150WebPList views, grids, cards
Medium600px widthWebPDetail pages, modals
OriginalPreservedWebPFull-size viewing

All variants are converted to WebP for optimal compression while maintaining quality.


Usage

Processing Images on Upload

typescript
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

typescript
// 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/jpg
  • image/png
  • image/webp
  • image/gif
  • image/avif

processImageVariants(buffer, mimetype): Promise<ImageVariants>

Processes an image buffer into thumbnail, medium, and optimized original.

Returns:

typescript
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, immutable

This 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:

typescript
{
  url: string;         // Original image URL
  thumbnailUrl?: string;
  mediumUrl?: string;
}

Frontend Usage

Use appropriate variant based on context:

typescript
// Grid view - use thumbnail
<img src={artist.thumbnailUrl || artist.image} />

// Detail page - use medium
<img src={artist.mediumUrl || artist.image} />

Performance Benchmarks

FormatOriginalWebP ThumbnailWebP MediumSavings
JPEG (2MB)2048 KB~5 KB~40 KB98%
PNG (4MB)4096 KB~6 KB~50 KB99%

Dependencies

bash
npm install sharp
npm install --save-dev @types/sharp

Last Updated: February 2026

Ctrl-Audio Platform Documentation