Serverless is traditionally used for very small functions that do one thing and do it well. But what if that one small thing has one large dependency? For example, what if your function depends on Headless Chrome? Can you really fit an entire web browser in a serverless function?

Goal

Our goal is to somehow ship a web browser and serverless function that can take a screenshot of any arbitrary website in under 50 MB. This might prove difficult because Google Chrome on MacOS is around 400 MB. Similarly, puppeteer is around 300 MB because it downloads Chromium during npm install before exposing the API. No surprise there.
Fortunately, the puppeteer documentation mentions a smaller puppeteer-core package, only 2 MB, that can connect to our own Chromium instance of choice. We can use that to take a screenshot, but we still need a super small version of Chromium that will run in a serverless environment.
Enter chrome-aws-lambda, a brotli-compressed version of Chromium designed for serverless environments, weighing in at only 35 MB! With a few lines of code, we can wire up these two packages to take a screenshot of a webpage!

Code

Let's create a /screenshot.js file with the following code.
const chrome = require('chrome-aws-lambda');
const puppeteer = require('puppeteer-core');

async function getScreenshot(url, type) {
    const browser = await puppeteer.launch({
        args: chrome.args,
        executablePath: await chrome.executablePath,
        headless: chrome.headless,
    });

    const page = await browser.newPage();
    await page.goto(url);
    const file = await page.screenshot({ type });
    await browser.close();
    return file;
}

module.exports = getScreenshot;
Looks great! Of course, we want to deploy this code to Now 2.0. So we'll need to export a single function that will read the HTTP Request and then write the HTTP Response. Think of this function as the same one that you pass to http.createServer(func) in any Node.js app.
Let's create a /api/index.js file with the following code.
const getScreenshot = require('./screenshot');

module.exports = async function (req, res) {
    const { pathname = '/', query = {} } = parse(req.url, true);
    const { type = 'png' } = query; // png or jpeg
    let url = pathname.slice(1);
    if (!url.startsWith('http')) {
        url = 'https://' + url; // add protocol if missing
    }
    const file = await getScreenshot(url, type);
    res.statusCode = 200;
    res.setHeader('Content-Type', \`image/\${type}\`);
    res.end(file);
};
That wasn't too difficult. Next, let's add the following now.json file.
{
    "version": 2,
    "routes": [
        { "src": "/(.*)", "dest": "/api/index.js" }
    ]
}
We use the routes key to add a regex source which will map all routes to our serverless function.
Let's deploy our code by running now from the command line.
When we visit our deployment with a route such as /google.com the result will be a screenshot of Google's homepage. You can try out the final result at screenshot-v2.now.sh.