Sharp
Process images at high performance with Sharp. Resize, crop, convert formats,
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 linesSharp 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
Related Skills
Canvas API
Draw graphics and generate images with the HTML Canvas 2D API and node-canvas.
Chromatic
Automate visual regression testing with Chromatic. Configure snapshot capture,
Design Tokens
Manage cross-platform design tokens with Style Dictionary. Define token schemas,
Figma API
Integrate with the Figma REST API and webhooks to read design files, extract components,
Iconify
Integrate icons at scale with the Iconify framework. Use framework-specific
Storybook
Develop and test UI components in isolation with Storybook. Configure stories,