Overview

This task optimizes and watermarks an image using the Sharp library, and then uploads the processed image to R2 storage.

Adding build configurations

To use this example, you’ll first need to add these build settings to your trigger.config.ts file:

trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk/v3";

export default defineConfig({
  project: "<project ref>",
  // Your other config settings...
  build: {
    // This is required to use the Sharp library
    external: ["sharp"],
  },
});

Any packages that install or build a native binary should be added to external, as native binaries cannot be bundled.

Key features

  • Resizes and rotates an image
  • Adds a watermark to the image
  • Uploads the processed image to R2 storage

Task code

trigger/sharp-image-processing.ts
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { logger, task } from "@trigger.dev/sdk/v3";
import fs from "fs/promises";
import fetch from "node-fetch";
import os from "os";
import path from "path";
import sharp from "sharp";

// Initialize R2 client
const r2Client = new S3Client({
  region: "auto",
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
  },
});

export const sharpProcessImage = task({
  id: "sharp-process-image",
  run: async (payload: { imageUrl: string; watermarkUrl: string }) => {
    const { imageUrl, watermarkUrl } = payload;

    // Generate temporary and output file names
    const tempDirectory = os.tmpdir();
    const outputPath = path.join(tempDirectory, `output_${Date.now()}.jpg`);

    // Fetch the image and watermark
    const [imageResponse, watermarkResponse] = await Promise.all([
      fetch(imageUrl),
      fetch(watermarkUrl),
    ]);
    const imageBuffer = await imageResponse.arrayBuffer();
    const watermarkBuffer = await watermarkResponse.arrayBuffer();

    // Optimize the image using Sharp
    await sharp(Buffer.from(imageBuffer))
      .rotate(90) // Rotate the image by 90 degrees
      .resize(800, 600) // Resize the image to 800x600
      .composite([
        {
          input: Buffer.from(watermarkBuffer),
          gravity: "southeast", // Position the watermark in the bottom-right corner
        },
      ])
      .toFormat("jpeg")
      .toFile(outputPath);

    // Log the output file path
    logger.log(`Optimized image saved at: ${outputPath}`);

    // Read the optimized image file
    const optimizedImageBuffer = await fs.readFile(outputPath);

    // Upload the optimized image to R2, replacing slashes with underscores
    const r2Key = `processed-images/${path.basename(outputPath)}`;

    const uploadParams = {
      Bucket: process.env.R2_BUCKET,
      Key: r2Key,
      Body: optimizedImageBuffer,
    };

    // Upload the image to R2 and get the URL
    await r2Client.send(new PutObjectCommand(uploadParams));
    const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
    logger.log("Optimized image uploaded to R2", { url: r2Url });

    // Delete the temporary file
    await fs.unlink(outputPath);

    // Return the optimized image buffer, file path, and R2 URL
    return {
      optimizedImageBuffer,
      optimizedImagePath: outputPath,
      r2Url,
    };
  },
});

Testing your task

To test this task in the dashboard, you can use the following payload:

{
  "imageUrl": "<an-image-url.jpg>", // Replace with a URL to a JPEG image
  "watermarkUrl": "<an-image-url.png>" // Replace with a URL to a PNG watermark image
}