Create a website screenshot service with Puppeteer on Heroku

7th Jannuary, 2020 - 5 min. read - in Tutorials - Go to Index

Hey, before scrolling, I've made a tool to help people building presentations the modern way.
If you don't mind, I'd like to ask some feedback about it. Here the starting point to learn more.
Much appreciated!

As the post title suggests, this is a step by step tutorial on how to create and run (on Heroku) a simple remote screenshot service.

The quickest way to set it up within your Heroku account is by following this README instruction from the repository.

But, since I’d like to document the dev and setup process in order to fix the things correctly in my mind, the following is a longer explanation.

Set Puppeteer locally

Puppeteer is a wonderful wrapper. Basically you can run Chromium at your service, locally and remotely as well.

Let’s dive first on set Puppeteer right alone.

Let’s create our application in an empty folder and with the shell:

const puppeteer = require('puppeteer')

module.exports = function (url) {
  return new Promise((resolve, reject) => {
    ;(async () => {
      const browser = await puppeteer.launch({
        // headless: true, // debug only
        args: ['--no-sandbox']
      })

      const page = await browser.newPage()

      await page.goto(url, {
        waitUntil: ['load', 'networkidle0', 'domcontentloaded']
      })

      await page.waitFor(1000)

      await page.emulateMedia('screen')

      const buffer = await page.screenshot({
        fullPage: true,
        type: 'png'
      })

      await browser.close()

      resolve(buffer)
    })()
  })
}

Now, create another file named test.js with the following code:

const screenshot = require('./screenshot')
const fs = require('fs')

;(async () => {
  const buffer = await screenshot('https://www.google.com')
  fs.writeFileSync('screenshot.png', buffer.toString('binary'), 'binary')
})()

And now, time to test the shit with node test.js

You should see after a while a screenshot of google.com within the project folder.

Put it in a webserver

The above test works using Node.js as command line. We want to expose the same functionality through a webserver, this way we can call the service from within the browser. Here the additional steps:

const express = require('express')
const app = express()
const port = process.env.PORT || 3131
const screenshot = require('./screenshot')

app.get('/', (req, res) => res.status(200).json({ status: 'ok' }))

app.get('/screenshot', (req, res) => {
  const url = req.query.url
  ;(async () => {
    const buffer = await screenshot(url)
    res.setHeader('Content-Disposition', 'attachment; filename="screenshot.png"')
    res.setHeader('Content-Type', 'image/png')
    res.send(buffer)
  })()
})

app.listen(port, () => console.log(`app listening on port ${port}!`))

You can test the webserver with the browser pointing it at http://localhost:3131 and the screenshot service with:

http://localhost:3131/screenshot?url=https://www.google.com

Now the browser should download automatically the png of the screenshot.

Time to deploy on Heroku

Now we have the complete app, here the steps to deploy it on Heroku:

It will take a while since it’s installing a full Chromium software.

After the deploy, click “Restart all dynos” under “more” dropdown.

If everything went fine, grab the app public URL and try to create your first screenshot using the same URL structure as before, such as:

https://my-app-000000.herokuapp.com/screenshot?url=https://www.google.com

Again, the png should be downloaded automatically, but now from a remote server.

From this point you can think about how to extend it, maybe providing the user the possibility to define the url of the website or something else.

Have a nice screenshot!


Spotted a typo or (likely) a grammar error? Send a pull request.