Інтеграція Sharp (Node.js) для серверної обробки зображень

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Інтеграція Sharp (Node.js) для серверної обробки зображень
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Інтеграція Sharp (Node.js) для серверної обробки зображень

Sharp — Node.js-бібліотека на базі libvips, на порядок швидша за Jimp або Canvas API. Обробляє JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG без втрати якості, не вимагає ImageMagick і працює в 4–5 разів швидше за аналоги при вдвічі меншому споживанні пам'яті.

Установка та базова конфігурація

npm install sharp
# Sharp постачається з передкомпільованими бінарниками libvips
# для Linux x64, macOS arm64, Windows x64

Sharp використовує потокову обробку — зображення не завантажується повністю в пам'ять:

const sharp = require('sharp')

// Базовий pipeline: resize + WebP + збереження
await sharp('./input/photo.jpg')
  .resize(800, 600, {
    fit: 'inside',           // вписати в рамку без обрізки
    withoutEnlargement: true // не збільшувати маленькі зображення
  })
  .webp({ quality: 82, effort: 4 })
  .toFile('./output/photo.webp')

Формати виходу та параметри якості

Формат Метод Рекомендована якість
JPEG .jpeg({ quality, mozjpeg }) 80–85, mozjpeg: true
WebP .webp({ quality, effort }) 80–85, effort: 4
AVIF .avif({ quality, effort }) 50–60, effort: 4
PNG .png({ compressionLevel }) compressionLevel: 6–8
// Генерація кількох форматів з одного джерела
async function convertToModernFormats(inputPath, outputDir, baseName) {
  const image = sharp(inputPath)
  const meta = await image.metadata()

  const resized = image.resize(1200, null, {
    fit: 'inside',
    withoutEnlargement: true
  })

  await Promise.all([
    // WebP для сучасних браузерів
    resized.clone()
      .webp({ quality: 82, effort: 4 })
      .toFile(`${outputDir}/${baseName}.webp`),

    // AVIF для Chrome/Firefox
    resized.clone()
      .avif({ quality: 55, effort: 4 })
      .toFile(`${outputDir}/${baseName}.avif`),

    // JPEG як fallback
    resized.clone()
      .jpeg({ quality: 85, mozjpeg: true })
      .toFile(`${outputDir}/${baseName}.jpg`),
  ])

  return { width: meta.width, height: meta.height }
}

Обробка EXIF та орієнтації

Sharp автоматично читає EXIF-орієнтацію, але не застосовує її за замовчуванням:

const image = sharp(buffer)
  .rotate()           // auto-rotate по EXIF orientation
  .withMetadata({     // зберегти метаданні (крім GPS якщо потрібна приватність)
    exif: {
      IFD0: { Copyright: 'My Company 2024' }
    }
  })

Для видалення GPS-даних при публічній публікації:

// Видалити всі метаданні (EXIF, IPTC, XMP)
.withMetadata(false)
// Або зберегти тільки ICC-профіль для коректних кольорів
.withMetadata({ icc: true })

Інтеграція з Multer (Express)

const multer = require('multer')
const { v4: uuidv4 } = require('uuid')

// Зберігати в пам'яті, обробляти Sharp до збереження на диск/S3
const upload = multer({ storage: multer.memoryStorage() })

app.post('/api/upload', upload.single('image'), async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'No file' })

  // Валідувати формат через метаданні (не MIME-заголовок — його можна підробити)
  let meta
  try {
    meta = await sharp(req.file.buffer).metadata()
  } catch {
    return res.status(422).json({ error: 'Invalid image' })
  }

  const allowedFormats = ['jpeg', 'png', 'webp', 'gif', 'avif']
  if (!allowedFormats.includes(meta.format)) {
    return res.status(422).json({ error: `Format ${meta.format} not allowed` })
  }

  const id = uuidv4()
  const variants = await processAndUpload(req.file.buffer, id)

  res.json({ id, variants })
})

async function processAndUpload(buffer, id) {
  const image = sharp(buffer).rotate() // EXIF auto-rotate

  const sizes = {
    thumb:  { width: 150, height: 150, fit: 'cover' },
    medium: { width: 800 },
    large:  { width: 1920 }
  }

  const results = {}

  for (const [name, dims] of Object.entries(sizes)) {
    const processed = await image.clone()
      .resize(dims.width, dims.height || null, {
        fit: dims.fit || 'inside',
        withoutEnlargement: true
      })
      .webp({ quality: 82, effort: 4 })
      .toBuffer()

    const key = `images/${id}/${name}.webp`
    await s3.putObject({
      Bucket: process.env.S3_BUCKET,
      Key: key,
      Body: processed,
      ContentType: 'image/webp',
      CacheControl: 'public, max-age=31536000, immutable'
    }).promise()

    results[name] = key
  }

  return results
}

Водяний знак та композитинг

async function addWatermark(imageBuffer, watermarkPath) {
  const image = sharp(imageBuffer)
  const { width, height } = await image.metadata()

  // Масштабувати watermark під 20% ширини зображення
  const wmSize = Math.floor(width * 0.2)

  const watermark = await sharp(watermarkPath)
    .resize(wmSize)
    .toBuffer()

  return image
    .composite([{
      input: watermark,
      gravity: 'southeast',
      blend: 'over'
    }])
    .toBuffer()
}

Продуктивність: Concurrency та потоки

Sharp за замовчуванням використовує всі CPU. У production слід обмежити:

sharp.concurrency(2) // максимум 2 потоки libvips

// Для високого навантаження — черга через p-limit
const pLimit = require('p-limit')
const limit = pLimit(4) // 4 паралельні завдання

const tasks = images.map(img =>
  limit(() => processImage(img))
)
const results = await Promise.all(tasks)

Обробка помилок

async function safeProcess(buffer) {
  try {
    const meta = await sharp(buffer).metadata()

    // Захист від decompression bomb
    if (meta.width * meta.height > 50_000_000) {
      throw new Error('Image too large: exceeds 50MP limit')
    }

    return await sharp(buffer)
      .resize(2000, 2000, { fit: 'inside', withoutEnlargement: true })
      .webp({ quality: 82 })
      .toBuffer()

  } catch (err) {
    if (err.message.includes('Input buffer contains unsupported image format')) {
      throw new TypeError('Unsupported image format')
    }
    throw err
  }
}

Термін виконання

Інтеграція Sharp з Multer, кількома форматами виходу та S3 upload — 1–2 робочі дні.