Every image uploaded to Boottify goes through an optimization pipeline before storage. The pipeline uses Sharp — the fastest Node.js image processing library — to resize, compress, strip metadata, and optionally convert to WebP. Different image types get different treatment.
WHY SHARP
We evaluated three options:
| Library | Speed (1920px resize) | Dependencies | Quality |
|---|---|---|---|
| Sharp | ~50ms | Native (libvips) | Excellent |
| Jimp | ~800ms | Pure JS | Good |
| Canvas | ~200ms | Native (Cairo) | Good |
Sharp is 16x faster than Jimp and produces better output quality thanks to libvips' Lanczos3 resampling algorithm. The native dependency is worth it.
OPTIMIZATION PROFILES
Different image types have different requirements. A blog cover image needs to be large and high-quality. An avatar needs to be small and square. We defined three profiles:
const PROFILES = {
blog: {
maxWidth: 1920,
maxHeight: 1080,
quality: 82,
format: "preserve", // Keep original format
},
avatar: {
maxWidth: 512,
maxHeight: 512,
quality: 80,
format: "webp",
},
logo: {
maxWidth: 1024,
maxHeight: 1024,
quality: 85,
format: "preserve",
},
};
THE OPTIMIZATION PIPELINE
The optimizeImage() function processes every upload through four stages:
1. Metadata Extraction
const metadata = await sharp(buffer).metadata();
const { width, height, format, orientation } = metadata;
We read dimensions and orientation to determine if resizing is needed and whether the image needs rotation (EXIF orientation fix).
2. EXIF Stripping
Uploaded photos often contain EXIF data: GPS coordinates, camera model, timestamps. This is a privacy concern — users don't expect their location to be embedded in a profile photo. Sharp strips all metadata by default with .rotate() (which also applies EXIF orientation).
3. Resize with Aspect Ratio Preservation
let pipeline = sharp(buffer).rotate(); // Apply EXIF orientation + strip
if (width > profile.maxWidth || height > profile.maxHeight) {
pipeline = pipeline.resize(profile.maxWidth, profile.maxHeight, {
fit: "inside",
withoutEnlargement: true,
});
}
The fit: "inside" option ensures the image fits within the bounding box while preserving aspect ratio. withoutEnlargement: true prevents small images from being scaled up — which would only add file size without improving quality.
4. Format-Specific Compression
// JPEG: mozjpeg compression
pipeline = pipeline.jpeg({ quality, mozjpeg: true });
// PNG: palette reduction
pipeline = pipeline.png({ quality, effort: 7 });
// WebP: lossy with effort tuning
pipeline = pipeline.webp({ quality, effort: 4 });
SVG files pass through unmodified — they're already vector and optimized at the source.
WEBP VS. JPEG COMPARISON
At the same perceptual quality, WebP is dramatically smaller:
| Image | JPEG (q82) | WebP (q80) | Savings |
|---|---|---|---|
| Blog cover (1920px) | 485KB | 142KB | 71% |
| Avatar (512px) | 68KB | 22KB | 68% |
| Logo (1024px) | 195KB | 58KB | 70% |
WebP consistently delivers 60-80% savings over JPEG. For thumbnails (120px width), the difference is even more dramatic — 5KB vs 25KB.
DARK BACKGROUND FLATTEN
Images with transparency (PNG, WebP) can look broken when rendered on our dark UI. Instead of leaving alpha channels intact, we flatten images onto a dark background that matches our design system:
pipeline = pipeline.flatten({ background: { r: 8, g: 12, b: 20 } }); // #080c14
This ensures transparent PNGs look intentional rather than broken on the platform.
INTEGRATION WITH UPLOAD API
The optimization function is called in every upload route before storage:
// POST /api/admin/blog/media
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
// Validate magic bytes match declared MIME type
if (!validateFileMagic(buffer, file.type)) {
throw ApiError.badRequest("File content does not match declared type");
}
// Optimize: resize, compress, strip EXIF
const optimized = await optimizeImage(buffer, file.type, "blog");
// Upload optimized version
const url = await uploadFile(key, optimized.buffer, contentType);
The caller doesn't need to know about Sharp, profiles, or compression settings. It passes a buffer and gets an optimized buffer back.
THE NUMBERS
- ~50ms processing time per image (Sharp + libvips)
- 60-80% file size reduction with WebP conversion
- EXIF data stripped from every upload (privacy protection)
- 3 optimization profiles tuned for blog, avatar, and logo contexts
- Zero visual quality loss at the chosen quality settings



