Zach’s ugly mug (his face)

Zach Leatherman

Building an Automated Screenshot Service on Netlify in ~140 Lines of Code

July 30, 2021 #15 Popular

This post is a continuation of the ideas first presented in How and Why I Removed 3000 Images from the Eleventy Docs Build.

The idea is pretty simple: a service that will accept a URL as input and return a static screenshot image of that URL to embed and use on other web sites. The code is pretty simple too, about 140 lines.

Having a service for these images is important as the Eleventy docs use a lot of visuals from Built With Eleventy sites around the web—it wouldn’t be feasible to generate these manually.

The end result looks something like this ( is shown):

Screenshot of

And the URL for the above image is

You can see this live in production now in a few different places on the Eleventy docs.

Decisions, decisions

I think there were a few architecture decisions that went into this service that are worth documenting, so here goes:

  1. This is now a separate repo and project from the main site. This is important as it decouples our On-demand Builder cache for this service away from the main web site, which deploys with a much higher frequency.
  2. This is best used with lower priority images, things that live further down the page (dare I say, below the imaginary fold). Works great with <img loading="lazy">. ⚠️ ABSOLUTELY not for use with HERO IMAGES or on something that might be eligible for your LCP!!! (I warned you with three exclamation marks.)
    • Best paired with preconnect: <link href="" rel="preconnect" crossorigin>.
  3. Sizing options are limited to improve cache hits. Currently we only offer 11 different image combinations for each URL. This will likely increase over time as we add additional options, like sizes or aspect ratios or maybe even a no-JavaScript mode. We want cache hits to make these things fast and reduce the request count to external web sites.
    • I added an Open Graph size (you know, for those cards that show up on social media posts). I’m currently playing around with this as a way to do super-lazy custom Open Graph images for every page. Each page can have an Open Graph image that’s a screenshot of itself!
  4. One negative of generating these in a serverless function is that image formats are a bit harder to manage. This means that only JPEG is supported for now. Especially with the version of Puppeteer that barely fits in a serverless bundle, I’m still trying to figure out how to bundle it with sharp and eleventy-img too.
  5. The entire thing is versioned using Netlify Branch subdomains: e.g. If I want to change the API later I’ll bump it to v2 and just leave the old branch as-is. Of particular note is that (without the version) redirects via an HTTP 301 to v1 and will do so permanently. Don’t rely on this redirect (for performance reasons).
  6. Update (July 30, 2021): The other issue I noticed with using Puppeteer in a Lambda is that emoji are not available to the rendered content. So if a site is using Emoji they do not render. It looks like Matic Jurglič may have a workaround to solve this.

What happens if a site is super slow or is currently down?

Netlify Functions have a 10 second execution limit. If the site doesn’t render in 10 seconds, we show a fallback image by default. Currently this is a low-contrast 11ty logo using the same image size as the requested screenshot (via SVG width and height attributes).

We don’t use a HTTP 500 status code on errors. In Firefox, the fallback image didn’t render when an error code was used. Because we aren’t using a HTTP 500 status code, the On-demand Builder will cache the fallback image for this request. This is good to prevent a bunch of re-requests to slow sites that don’t make the cutoff (or have a different error) but also means if a request had an outlier response time then the fallback image will continue to be used until the On-demand Builder cache is invalidated with a new build.

We include the real error message in a custom x-error-message HTTP Header, if you want more insight into why a screenshot failed.

Can I Use Your Instance For My Site?

Um… I’m not sure yet. For now I’d recommend just self hosting it. You can click this button to do it:

Deploy to Netlify

The full source code is available on GitHub.


Small (375px viewport width)

Screenshot of

Medium (650px viewport width)

Screenshot of

Large (1024px viewport width)

Screenshot of

Open Graph (1200×630)

Zach’s ugly mug (his face)

Zach is a builder for the web with IndieWeb Avatar for https://www.netlify.comNetlify. He created the IndieWeb Avatar for https://www.11ty.devEleventy site generator and is still fixated on web fonts. His public speaking résumé includes talks in eight different countries at events like IndieWeb Avatar for Conf,btconf’s AvatarBeyond Tellerrand, IndieWeb Avatar for Conference,CSSConf’s AvatarCSSConf, and IndieWeb Avatar for https://www.whitehouse.govThe White House. He is an emeritus of IndieWeb Avatar for https://www.filamentgroup.comFilament Group, nejsconf’s AvatarNEJS CONF, and still helps out with nebraskajs’s AvatarNebraskaJS. Read more about Zach »

If I work really hard on my Open Graph Images, People will share my Blog Posts
Uniclode: yet another demo of Eleventy Serverless


Iain FreestoneMatt BiilmannWill MendesEliad MoosaviEleventy
Tomek SułkowskiPrince WilsonZacabk SeuberlingJames BatesonViniciusDallacqua 🌐Tristan RemyAdam GreenoughPaul J Stales 🧑🏻‍💻Iain Beankeith h.Gaël PoupardargelEric WallaceKen Doman𝒟𝑜𝑔𝓊𝓀𝒶𝓃elvendrimMathias RechtzigelSia KaramalegosTanner DolbyDave Pedu𝕕𝔾𝕣𝕒𝕞𝕞𝕒𝕥𝕚𝕜𝕠Yannis Spyrou˗ˏˋ ˊˎ˗TRST_BlogMatt BiilmannCarles MuiñosEugene ChulkovWill MendesTRST_BlogNick SollecitoMike AparicioNaomi See 🤖👩🏻‍🎤❤️🤦‍♀️🤷🏻‍♀️John MeyerhoferWinston FassettAlex PageJakeBryce Wray Brian RinaldiMatt BiilmannMatthias OttJens GrochtdreisTrent WaltonAravind Reddy V (vi/vim)Mohammed Zeeshan🌸🌕⭐🌕🌸olivMNate van der VisPatrick HaugPhil HawksworthEleventyGeorge GriffithsDavid O'HalloranSia KaramalegosChris McMahonNikhil MehtaMatt Strömchristina 🥚Steve LeeStephanie Eckles🧚🏽‍♀️ 𝔭𝔞𝔯𝔢𝔢𝔫𝔞 🧚🏽‍♀️Andrew MasonAshiateyMaxDave RupertFilipi
9 Replies
  1. Zach Leatherman

    Zach Leatherman @zachleat #

    > automatically cached on Netlify’s Edge CDN via…

  2. Dave Rupert

    Dave Rupert @davatron5000 #

    ❤️ it! How do you invalidate or update the screenshot when changes happen?

  3. David Wells

    David Wells @DavidWells #

    That’s pretty nifty! Where is the builder handler wrapper saving the images to?

  4. Sia Karamalegos

    Sia Karamalegos @TheGreenGreek #

    I'd probably turn it into an api that only accepts requests from a specific domain so you don't go over your limits lol. It looks like a fun thing to try to code. I'd play with it but have too many things I'm fiddling with right now! 😅

  5. Zach Leatherman

    Zach Leatherman @zachleat #

    Ooh, yeah, that’s a good idea 🙌🏻

  6. Sia Karamalegos

    Sia Karamalegos @TheGreenGreek #

    Like a fancier version of this…

  7. Sia Karamalegos

    Sia Karamalegos @TheGreenGreek #

    This is really cool. Another option could be to generate 1 image then use a cloudinary proxy to provide different sizes and formats

  8. Zach Leatherman

    Zach Leatherman @zachleat #

    Ack, thank you! deploying

  9. Sia Karamalegos

    Sia Karamalegos @TheGreenGreek #

    Very cool! Typo: Words great with

    19 Mentions
    1. #

    2. #

    3. #

      Show original post Open Graph images. Those little images that show up when your site is shared on social media. The ones for my site were… bad. My default was to use one, huge, boring branded image for everything—which provided no extra value and just cluttered up y’alls feeds (sorry!). I wanted t… Truncated

    4. #

    5. #

    6. #

    7. #

      Show original post Facebook Twitter LinkedIn Pinterest … Truncated

    8. #

    9. #

    10. #

    11. #

    12. #

    13. #

    14. #

    15. admin #

      OPEn GrAPh IMAges. ThosE littLE ImaGES THAt shoW up wHeN yoUR site IS SHARed On sOcial mEdIa. thE ONEs For mY Site wERe… Bad. MY DeFaUlT WAS TO uSE One, HuGe, boRing brandED imAGe FOR EverYTHING—WhicH pROvideD no ExTRA vaLue aNd JuSt ClUttErED Up y’aLlS Feeds (sorRY!). i waNTEd t… Truncated

    16. Fernando - Código Falado

      Fernando - Código Falado @CodigoFalado #

      Fazendo um serviço automatizado de screenshots com 140 linhas de código (em inglês) -…

    17. Frontend Daily 🚀

      Frontend Daily 🚀 @FrontendDaily #

      Building an Automated Screenshot Service on Netlify in ~140 Lines of Code:…

    18. Christophe Jean

      Christophe Jean @cjean #…

    19. M157q News RSS

      M157q News RSS @M157q_News_RSS #

      Building An Automated Screenshot Service On Netlify In ~140 Lines Of Code… This blog post is adapted from:… The idea is pretty simple: a service that will accept a URL as input and return a s…

    Social Card Image Preview

    This is what will show up when you share this post on Social Media:

    How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)