Fix: Sharp Not Working — Installation Failing, Image Not Processing, or Build Errors on Deploy
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix Sharp image processing issues — native binary installation, resize and convert operations, Next.js image optimization, Docker setup, serverless deployment, and common platform errors.
The Problem
Sharp fails to install:
npm install sharp
# Error: Could not load the "sharp" module using the linux-x64 runtime
# Or: sharp: Installation error: command failedOr image processing throws at runtime:
import sharp from 'sharp';
await sharp('input.jpg').resize(300, 200).toFile('output.jpg');
// Error: Input file is missing or of an unsupported image formatOr the build works locally but fails in CI/Docker:
Error: Something went wrong installing the "sharp" package
Could not load the "sharp" module using the linuxmusl-x64 runtimeProduction Incident: Every Image on the Site Goes Down
When Sharp breaks, the blast radius is enormous. On Next.js, next/image uses Sharp for runtime optimization. A failed Sharp install on the deploy host means every <Image> component on the site stops returning bytes — your homepage hero, product thumbnails, blog covers, OG images, all of them. Search bots see broken images, Lighthouse drops by 30 points, and conversion takes a measurable hit within hours.
The classic incident pattern: a Mac developer runs npm install locally, commits package-lock.json, and pushes. The CI runner is Linux, the lockfile records darwin-arm64 binaries, and the build either fails with MODULE_NOT_FOUND or silently ships a binary the Linux runtime can’t load. The first request to a /_next/image URL returns 500. Page-on-call should fire on Sharp-specific error strings (Could not load the sharp module, Input file is missing) in your image-pipeline logs.
On AWS Lambda the failure mode is different: the function package contains the wrong libvips ABI, so the cold start completes but the first image transform throws Input buffer contains unsupported image format. Because cold starts are rare in steady state, you sometimes ship a broken deploy and only discover it during the next traffic spike. Always run a Sharp smoke test in your deploy pipeline — resize a known 16x16 PNG and assert the output dimensions before promoting the build.
Why This Happens
Sharp is a high-performance image processing library built on libvips. It uses pre-built native binaries for each platform:
- Sharp downloads platform-specific binaries — during
npm install, Sharp downloads a pre-compiled binary for your OS, architecture, and C library (glibc vs musl). If the download fails or the platform isn’t supported, installation errors occur. - Docker/CI has a different platform than your dev machine — installing Sharp on macOS (darwin-arm64) and deploying to Linux Docker (linux-x64 or linuxmusl-x64) means the local binary won’t work. Dependencies must be installed inside the target environment.
- Sharp can’t process files that don’t exist or are corrupted — paths must be absolute or relative to the working directory. Buffer inputs must contain valid image data. URL inputs aren’t supported directly — fetch first, then pass the buffer.
- Next.js uses Sharp internally —
next/imageoptimization uses Sharp in production. If Sharp isn’t installed or misconfigured, image optimization falls back to a slower implementation or fails.
A deeper reason platform mismatches are so common: npm install resolves optional dependencies per platform. Sharp v0.33 and later ships separate packages like @img/sharp-linux-x64, @img/sharp-linuxmusl-x64, @img/sharp-darwin-arm64, and so on. The package.json lists all of them under optionalDependencies, and npm picks only the one matching the current OS. When you commit a lockfile generated on macOS and rebuild on Linux without re-installing, the right binary is never on disk. The fix is to install on the target platform or use --include=optional with explicit --os and --cpu flags.
The third class of failures is memory. libvips is famously memory-light because it streams pixels through a pipeline instead of decoding the whole image into RAM, but very large source images (40+ megapixel scans, 30000x30000 panoramas) can still blow past Lambda’s 512MB tmp or a small container’s heap. Sharp also caches operations internally; in a long-running web server, repeated calls without sharp.cache(false) can grow process RSS by hundreds of megabytes. Watch RSS on your image worker — if it grows monotonically, disable the cache.
Fix 1: Correct Installation
# Standard install
npm install sharp
# Force rebuild (if platform changed)
npm rebuild sharp
# Specific platform (for cross-compilation or CI)
npm install --platform=linux --arch=x64 sharp
npm install --platform=linux --arch=arm64 sharp
npm install --platform=linuxmusl --arch=x64 sharp # Alpine Linux
# If behind a corporate proxy
npm install sharp --sharp-libvips-binary-host="https://your-mirror.com"
# Clear cache and reinstall
npm cache clean --force
rm -rf node_modules/sharp
npm install sharpFix 2: Basic Image Operations
import sharp from 'sharp';
// Resize
await sharp('input.jpg')
.resize(800, 600) // width, height
.toFile('output.jpg');
// Resize maintaining aspect ratio
await sharp('input.jpg')
.resize(800) // Width only — height auto-calculated
.toFile('output.jpg');
// Resize with fit options
await sharp('input.jpg')
.resize(800, 600, {
fit: 'cover', // 'cover' | 'contain' | 'fill' | 'inside' | 'outside'
position: 'center', // 'top' | 'right' | 'bottom' | 'left' | 'center' | 'entropy' | 'attention'
})
.toFile('output.jpg');
// Convert format
await sharp('input.png')
.jpeg({ quality: 80, progressive: true })
.toFile('output.jpg');
await sharp('input.jpg')
.webp({ quality: 80, effort: 4 })
.toFile('output.webp');
await sharp('input.jpg')
.avif({ quality: 60 })
.toFile('output.avif');
// From buffer (for API routes / file uploads)
const inputBuffer = await file.arrayBuffer();
const outputBuffer = await sharp(Buffer.from(inputBuffer))
.resize(400, 400, { fit: 'cover' })
.webp({ quality: 80 })
.toBuffer();
// Get image metadata
const metadata = await sharp('input.jpg').metadata();
console.log(metadata.width, metadata.height, metadata.format);
// 4032, 3024, 'jpeg'
// Pipeline — chain operations
await sharp('input.jpg')
.resize(800, 600, { fit: 'cover' })
.blur(2)
.sharpen()
.normalize()
.gamma(1.5)
.webp({ quality: 80 })
.toFile('output.webp');Fix 3: Generate Thumbnails and Responsive Images
import sharp from 'sharp';
import path from 'path';
// Generate multiple sizes for responsive images
async function generateResponsiveImages(inputPath: string, outputDir: string) {
const sizes = [
{ width: 320, suffix: 'sm' },
{ width: 640, suffix: 'md' },
{ width: 1024, suffix: 'lg' },
{ width: 1920, suffix: 'xl' },
];
const filename = path.basename(inputPath, path.extname(inputPath));
const results = await Promise.all(
sizes.map(async ({ width, suffix }) => {
const outputPath = path.join(outputDir, `${filename}-${suffix}.webp`);
await sharp(inputPath)
.resize(width, null, { withoutEnlargement: true })
.webp({ quality: 80 })
.toFile(outputPath);
return { width, path: outputPath };
})
);
return results;
}
// Generate thumbnail with blur placeholder
async function generateThumbnailWithPlaceholder(inputPath: string) {
// Full thumbnail
const thumbnail = await sharp(inputPath)
.resize(300, 300, { fit: 'cover' })
.webp({ quality: 75 })
.toBuffer();
// Tiny blur placeholder (for LQIP — Low Quality Image Placeholder)
const placeholder = await sharp(inputPath)
.resize(10, 10, { fit: 'cover' })
.blur()
.toBuffer();
const placeholderBase64 = `data:image/webp;base64,${placeholder.toString('base64')}`;
return { thumbnail, placeholderBase64 };
}Fix 4: API Route for Image Processing
// app/api/image/route.ts — on-the-fly image processing
import sharp from 'sharp';
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const url = searchParams.get('url');
const width = parseInt(searchParams.get('w') || '800');
const height = searchParams.get('h') ? parseInt(searchParams.get('h')!) : undefined;
const quality = parseInt(searchParams.get('q') || '80');
const format = searchParams.get('f') || 'webp';
if (!url) return new Response('Missing url parameter', { status: 400 });
try {
// Fetch the source image
const response = await fetch(url);
if (!response.ok) return new Response('Image not found', { status: 404 });
const buffer = Buffer.from(await response.arrayBuffer());
// Process
let pipeline = sharp(buffer).resize(width, height, {
fit: 'cover',
withoutEnlargement: true,
});
// Output format
switch (format) {
case 'webp': pipeline = pipeline.webp({ quality }); break;
case 'avif': pipeline = pipeline.avif({ quality }); break;
case 'png': pipeline = pipeline.png({ quality }); break;
default: pipeline = pipeline.jpeg({ quality, progressive: true });
}
const output = await pipeline.toBuffer();
return new Response(output, {
headers: {
'Content-Type': `image/${format}`,
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
} catch (error) {
return new Response('Processing failed', { status: 500 });
}
}
// Usage: /api/image?url=https://example.com/photo.jpg&w=400&q=75&f=webpFix 5: Docker Configuration
# Dockerfile — Sharp works in standard Node images
FROM node:20-slim
# Sharp dependencies are pre-installed in node:20-slim
# For Alpine (node:20-alpine), install vips:
# FROM node:20-alpine
# RUN apk add --no-cache vips-dev
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]# Multi-stage build — install dependencies in target platform
FROM node:20-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-slim AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "dist/index.js"]// package.json — ensure Sharp installs for the deploy platform
{
"scripts": {
"postinstall": "npm rebuild sharp"
}
}Fix 6: Watermark and Composite
import sharp from 'sharp';
// Add watermark
async function addWatermark(inputPath: string, watermarkPath: string) {
const watermark = await sharp(watermarkPath)
.resize(200) // Resize watermark
.ensureAlpha(0.5) // Semi-transparent
.toBuffer();
await sharp(inputPath)
.composite([{
input: watermark,
gravity: 'southeast', // Bottom-right corner
blend: 'over',
}])
.toFile('watermarked.jpg');
}
// Add text watermark (using SVG)
async function addTextWatermark(inputPath: string, text: string) {
const metadata = await sharp(inputPath).metadata();
const svg = `
<svg width="${metadata.width}" height="${metadata.height}">
<text
x="50%" y="95%"
text-anchor="middle"
font-family="Arial"
font-size="24"
fill="white"
opacity="0.7"
>${text}</text>
</svg>
`;
await sharp(inputPath)
.composite([{ input: Buffer.from(svg), blend: 'over' }])
.toFile('watermarked.jpg');
}
// Create OG image (combine background + text)
async function createOGImage(title: string) {
const width = 1200;
const height = 630;
// Create background
const background = sharp({
create: { width, height, channels: 4, background: { r: 26, g: 26, b: 46, alpha: 1 } },
});
// Text as SVG
const textSvg = `
<svg width="${width}" height="${height}">
<text x="60" y="${height / 2}" font-family="Arial" font-size="48" font-weight="bold" fill="white">
${escapeXml(title)}
</text>
</svg>
`;
return background
.composite([{ input: Buffer.from(textSvg), blend: 'over' }])
.png()
.toBuffer();
}Still Not Working?
“Could not load the sharp module using the linux-x64 runtime” — Sharp’s native binary doesn’t match the platform. Run npm rebuild sharp in the target environment. In Docker, install dependencies inside the container, not on the host. For serverless (Vercel, Netlify), Sharp is usually pre-installed.
“Input file is missing” — Sharp resolves paths relative to process.cwd(), not the script location. Use absolute paths: path.join(process.cwd(), 'public', 'image.jpg'). For buffers, ensure the buffer contains valid image data, not an HTML error page.
Sharp slows down the build on Vercel/Netlify — Sharp installation downloads a ~25MB native binary. Add sharp to your package.json dependencies (not devDependencies) so it’s available at runtime. Vercel auto-installs Sharp for Next.js projects.
Memory errors on large images — Sharp streams data efficiently, but very large images (50MP+) can exceed memory limits. Use sharp.limitInputPixels(false) to remove the pixel limit, or resize before processing. For batch operations, process images sequentially, not in parallel.
RSS grows over time in a long-running server — Sharp keeps an internal operations cache. In a worker handling hundreds of unique images, call sharp.cache(false) at startup, or set explicit limits with sharp.cache({ memory: 50, files: 20, items: 100 }). Also call sharp.concurrency(1) on small containers where parallel libvips threads contend for the same CPU.
Lambda cold starts time out on first transform — the Lambda package layout matters. Sharp’s libvips binary must live in node_modules/@img/sharp-linux-x64 (and @img/sharp-libvips-linux-x64) inside the deploy bundle. Build the bundle on a Linux runner, or use npm install --include=optional --os=linux --cpu=x64 sharp from your build script. The next-on-lambda adapters do this automatically; custom bundlers don’t.
ARM64 vs x64 mismatch on M-series Macs — when a Mac developer’s Docker build inherits the host architecture, the resulting image is linux/arm64 even if your fleet runs linux/amd64. Either build with docker buildx build --platform=linux/amd64, or add a CI build step that always installs Sharp for the target architecture.
Output is correct but file size is huge — Sharp’s defaults trade off speed for size. For WebP, increase effort from the default (4) to 6 — it takes 2-3x longer but produces 10-20% smaller files. For AVIF, set effort: 9 and quality: 50 for hero images; the compression is dramatic and the visual quality holds up. Profile the wins with Content-Length headers before and after; don’t accept a CDN configuration that strips them.
Smoke test fails only on first deploy after dependency bump — Sharp’s libvips bundle changes between minor versions, and a package-lock.json regenerated on a Mac may pin platform-specific entries that aren’t installable on Linux. Pin Sharp to an exact version in package.json ("sharp": "0.33.5", not "^0.33.5"), and rebuild the lockfile inside a Linux container before committing. This keeps your CI and prod environments in lockstep.
For related image and deployment issues, see Fix: Next.js Image Optimization Error, Fix: Blurhash Not Working, Fix: AWS Lambda Timeout, and Fix: Vercel Deployment Failed.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Clack Not Working — Prompts Not Displaying, Spinners Stuck, or Cancel Not Handled
How to fix @clack/prompts issues — interactive CLI prompts, spinners, multi-select, confirm dialogs, grouped tasks, cancellation handling, and building CLI tools with beautiful output.
Fix: pdf-lib Not Working — PDF Not Generating, Fonts Not Embedding, or Pages Blank
How to fix pdf-lib issues — creating PDFs from scratch, modifying existing PDFs, embedding fonts and images, form filling, merging documents, and browser and Node.js usage.
Fix: Pusher Not Working — Events Not Received, Channel Auth Failing, or Connection Dropping
How to fix Pusher real-time issues — client and server setup, channel types, presence channels, authentication endpoints, event binding, connection management, and React integration.