How to upload a screenshot from Puppeteer to Cloudinary

In my article yesterday, I wrote about the problem I was trying to solve with my caniuse embed. To recap, I was trying to create a fallback image for the live embed and I did this by taking a screenshot of the page using puppeteer.

Yesterday, I covered how to use headless Chrome, via Puppeteer, to capture that screenshot. The next thing I needed to do was upload the screenshot to my Cloudinary account in order to have a hosted image that could be referenced.

This is quite a specific problem I had to solve, but I couldn’t find any examples or documentation of how to do it, so I thought it would be useful to share the process.

Save the screenshot as a buffer

Using Puppeteer, we can capture a screenshot and save it to disk using the page.screenshot() method. However, in my use case, I didn’t need to save the image to the file system. Instead, I wanted to capture it as a buffer, which could then be uploaded to Cloudinary.

The page.screenshot() method returns a buffer, so all we need to do is save it.

const screenshotBuffer = await page.screenshot({
    encoding: 'binary',
    // other options...
});

// Do something with screenshotBuffer

Upload the stream to Cloudinary

Next, we need to upload the buffer to Cloudinary. The Cloudinary node module has a specific method for uploading images as streams, the upload_stream method.

const cloudinary = require('cloudinary').v2; // Make sure to use v2

cloudinary.config({
  cloud_name: YOUR_CLOUDINARY_CLOUD_NAME, 
  api_key: YOUR_CLOUDINARY_API_KEY, 
  api_secret: YOUR_CLOUDINARY_API_SECRET
});

cloudinary.uploader.upload_stream(
  uploadOptions, 
  (error, result) => { /* Do something with result! */ }
).end(screenshotBuffer);

If successful, the result will be an object with information about the image, such as the url!

Putting it all together in an API

Since we are working with private keys and API secrets, all of this should ideally be done server-side. I created a simple express application to handle everything from taking the screenshot to uploading the image, then returning the resulting image data from Cloudinary.

You can view the full API on my GitHub repository, caniuse-embed-screenshot-api, but here's a stripped-down version of the basic functionality:

/* main.js */
const express = require("express");
const app = express();

const puppeteer = require('puppeteer');
const cloudinary = require('cloudinary').v2;
cloudinary.config(/* config here */);

// Create an express route
app.post("/upload", (req, res) => {
  takeScreenshot()
    .then((screenshot) => uploadScreenshot(screenshot))
    .then((result) => res.status(200).json(result));
});

// See https://bitsofco.de/using-a-headless-browser-to-capture-page-screenshots
async function takeScreenshot() {
  const browser = await puppeteer.launch({
    defaultViewport: {
      width: 800,
      height: 800,
      isLandscape: true
    }
  });

  const page = await browser.newPage();

  await page.goto(
    embedUrl,
    { waitUntil: 'networkidle2' }
  );

  const screenshot = await page.screenshot({
    omitBackground: true,
    encoding: 'binary'
  });

  await browser.close();

  return screenshot;
}

function uploadScreenshot(screenshot) {
  return new Promise((resolve, reject) => {
    const uploadOptions = {};
    cloudinary.uploader.upload_stream(uploadOptions, (error, result) => {
      if (error) reject(error)
      else resolve(result);
    }).end(screenshot);
  });
}
blog comments powered by Disqus