Skip to content

Fix: Sharp Not Working — Installation Failing, Image Not Processing, or Build Errors on Deploy

FixDevs ·

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 failed

Or 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 format

Or 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 runtime

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 internallynext/image optimization uses Sharp in production. If Sharp isn’t installed or misconfigured, image optimization falls back to a slower implementation or fails.

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 sharp

Fix 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=webp

Fix 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.

For related image issues, see Fix: Next.js Image Optimization Error and Fix: Blurhash Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles