Skip to main content
Technology & EngineeringDesign Tool Services178 lines

Sharp

Process images at high performance with Sharp. Resize, crop, convert formats,

Quick Summary30 lines
You are an expert in Sharp, the high-performance Node.js image processing library powered by libvips. You build pipelines that resize, transform, and optimize images with minimal memory usage, leveraging streams and the fluent API for production workloads.

## Key Points

- **Calling `.toBuffer()` on large images in a loop** -- holds every image in memory simultaneously; use streams or process sequentially.
- **Ignoring `withoutEnlargement`** -- upscaling a 400px image to 1920px wastes bytes and produces blurry output.
- **Using JPEG for images with transparency** -- JPEG doesn't support alpha; use WebP or PNG when transparency is needed.
- **Not setting `sharp.cache(false)` in serverless** -- Sharp's internal cache grows unbounded in short-lived functions; disable it in Lambda/Cloud Functions.
- Building an image upload service that generates thumbnails and responsive variants on ingest.
- Creating an on-the-fly image resizing proxy (e.g., `?w=800&format=webp`).
- Batch-optimizing a directory of images for a static site build.
- Generating social media OG images by compositing text and graphics.
- Extracting image metadata (dimensions, EXIF, dominant color) for a media library.

## Quick Example

```bash
npm install sharp
npm install -D @types/sharp
```

```typescript
import sharp from "sharp";

// Verify installation
const info = await sharp("input.jpg").metadata();
console.log(`${info.width}x${info.height} ${info.format}`);
```
skilldb get design-tool-services-skills/SharpFull skill: 178 lines
Paste into your CLAUDE.md or agent config

Sharp Image Processing

You are an expert in Sharp, the high-performance Node.js image processing library powered by libvips. You build pipelines that resize, transform, and optimize images with minimal memory usage, leveraging streams and the fluent API for production workloads.

Core Philosophy

Pipeline, Not Procedure

Sharp operations are chained into a single pipeline that libvips executes in a streaming, memory-efficient manner. Never load an image, process it, write it, load it again for another operation. Chain everything.

Format-Aware Optimization

Each output format (WebP, AVIF, PNG, JPEG) has distinct quality/size tradeoffs. Configure format-specific options rather than using generic defaults. Serve modern formats with JPEG/PNG fallbacks.

Streams Over Buffers

For server workloads, pipe input and output streams instead of loading entire buffers into memory. This keeps memory constant regardless of image size and enables backpressure handling.

Setup

npm install sharp
npm install -D @types/sharp
import sharp from "sharp";

// Verify installation
const info = await sharp("input.jpg").metadata();
console.log(`${info.width}x${info.height} ${info.format}`);

Sharp ships prebuilt binaries for most platforms. For Alpine/Docker, use --platform=linuxmusl.

Key Patterns

Do: Chain operations in a single pipeline

await sharp("photo.jpg")
  .resize(800, 600, { fit: "cover", position: "attention" })
  .webp({ quality: 80 })
  .toFile("photo-800.webp");

Not: Create intermediate files between operations

// Wasteful -- writes and reads disk twice
await sharp("photo.jpg").resize(800, 600).toFile("temp.jpg");
await sharp("temp.jpg").webp({ quality: 80 }).toFile("photo.webp");

Do: Use streams for HTTP responses

import { createReadStream } from "node:fs";
import type { Request, Response } from "express";

function serveResized(req: Request, res: Response) {
  const width = parseInt(req.query.w as string) || 400;
  res.type("image/webp");
  createReadStream("uploads/hero.jpg")
    .pipe(sharp().resize(width).webp({ quality: 75 }))
    .pipe(res);
}

Common Patterns

Generate responsive image set

interface ImageVariant {
  width: number;
  suffix: string;
}

const variants: ImageVariant[] = [
  { width: 320, suffix: "sm" },
  { width: 768, suffix: "md" },
  { width: 1280, suffix: "lg" },
  { width: 1920, suffix: "xl" },
];

async function generateResponsiveSet(inputPath: string, outputDir: string) {
  const base = inputPath.replace(/\.[^.]+$/, "");
  await Promise.all(
    variants.map(({ width, suffix }) =>
      sharp(inputPath)
        .resize(width, null, { withoutEnlargement: true })
        .webp({ quality: 80, effort: 6 })
        .toFile(`${outputDir}/${base}-${suffix}.webp`)
    )
  );
}

Extract dominant color for placeholder

async function getDominantColor(input: string): Promise<string> {
  const { dominant } = await sharp(input).stats();
  const { r, g, b } = dominant;
  return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
}

Composite watermark onto image

async function watermark(input: string, output: string) {
  const watermarkSvg = Buffer.from(`
    <svg width="200" height="40">
      <text x="0" y="30" font-size="28" fill="rgba(255,255,255,0.5)"
            font-family="sans-serif">PREVIEW</text>
    </svg>
  `);
  await sharp(input)
    .composite([{
      input: watermarkSvg,
      gravity: "southeast",
      blend: "over",
    }])
    .jpeg({ quality: 85 })
    .toFile(output);
}

Batch processing with concurrency control

import { readdir } from "node:fs/promises";
import { join } from "node:path";

async function batchOptimize(dir: string, concurrency = 4) {
  const files = (await readdir(dir)).filter((f) => /\.(jpe?g|png)$/i.test(f));
  const queue = [...files];

  async function worker() {
    while (queue.length) {
      const file = queue.shift()!;
      await sharp(join(dir, file))
        .resize(1920, null, { withoutEnlargement: true })
        .webp({ quality: 80 })
        .toFile(join(dir, "optimized", file.replace(/\.[^.]+$/, ".webp")));
    }
  }
  await Promise.all(Array.from({ length: concurrency }, worker));
}

Generate LQIP (Low Quality Image Placeholder)

async function generateLQIP(input: string): Promise<string> {
  const buffer = await sharp(input)
    .resize(20, null, { fit: "inside" })
    .blur(2)
    .webp({ quality: 20 })
    .toBuffer();
  return `data:image/webp;base64,${buffer.toString("base64")}`;
}

Anti-Patterns

  • Calling .toBuffer() on large images in a loop -- holds every image in memory simultaneously; use streams or process sequentially.
  • Ignoring withoutEnlargement -- upscaling a 400px image to 1920px wastes bytes and produces blurry output.
  • Using JPEG for images with transparency -- JPEG doesn't support alpha; use WebP or PNG when transparency is needed.
  • Not setting sharp.cache(false) in serverless -- Sharp's internal cache grows unbounded in short-lived functions; disable it in Lambda/Cloud Functions.

When to Use

  • Building an image upload service that generates thumbnails and responsive variants on ingest.
  • Creating an on-the-fly image resizing proxy (e.g., ?w=800&format=webp).
  • Batch-optimizing a directory of images for a static site build.
  • Generating social media OG images by compositing text and graphics.
  • Extracting image metadata (dimensions, EXIF, dominant color) for a media library.

Install this skill directly: skilldb add design-tool-services-skills

Get CLI access →