Skip to content

Now with Dynamic Open Graph Images!

Inspired by GitHub's pretty sweet post about their Open Graph images framework, I decided to try to build something similar for my site (at a smaller scale, obviously!). I wanted to build this for my blog ever since I saw the folks at Vercel do it.

I used Puppeteer to take a screenshot of a very simple HTML template, hooked it into my website's deployment pipeline, and voilà: dynamic OG images!

Dynamically-generated image shows the title of this post, "Now With Dynamic Open Graph Images!" in large bold font. Above the title is the date when it was published, Thursday, June 24th, 2021

There's still some tweaks I wanna make to the template to make it nicer, but it's looking clean!

I wrote generator code that gets executed as part of my next export process, this is the full script:

import chromium from "chrome-aws-lambda";
import format from "date-fns/format";
import fs from "fs";
import path from "path";
import posts from "./posts";

export async function generate({ outDir }) {
  const browser = await chromium.puppeteer.launch({
    args: chromium.args,
    executablePath: await chromium.executablePath,
    headless: true,

  const page = await browser.newPage();

  // Open the HTML template as a data URI
  await page.goto(
    { waitUntil: "networkidle0" }

  // Wait until the document is fully rendered
  await page.evaluateHandle("document.fonts.ready");

  // Set the viewport size to match
  await page.setViewport({
    width: 1200,
    height: 632,
    // Netlify build machines do not have a
    // retina display, apparently xD
    deviceScaleFactor: process.env.NETLIFY ? 1 : 2,

  const ogImagesDir = path.resolve(outDir, OG_IMAGES_PATH);

  // iterate over each post
  for (const post of posts) {
    const info = {
      title: post.title,
      date: format(, "dddd, MMMM Do, YYYY"),

    await page.evaluate((info) => {
      const titleElement = document.querySelector("#title");
      const dateElement = document.querySelector("#date");
      titleElement.innerHTML = info.title;
      dateElement.innerHTML =;
    }, info);

    await page.screenshot({
      path: `${ogImagesDir}/${}.png`,
      type: "png",
      clip: { x: 0, y: 0, width: 1200, height: 632 },

  // Wrap it up
  await browser.close();

This code heavily inspired by vercel/og-image's code.