Create dynamic open graph images for NextJS pages at build time

May 17, 20244 min read#react, #javascript, #nextjs

Problem

The landing page of my Quick Drop app is created using NextJS.

Recently, I have started doing some content marketing for my app. One of the task is to create Open Graph banner images for the blog, that I’m setting up for Quick Drop.

After some research, it looks like there is an issue with generating of open graph images when the NextJS is configured with output: "export"

After some more research and with the help of ChatGPT, I can finally find a temporary solution for this issue. The plan is to create a node script to pre-generate all open graph images for all blog pages. The OG image will have the content adapted dynamically to every blog post, with the title is rendered on top of a template.

Solution

The solution consists of several html, javascript font and image files:

folder

  • The Inter font files are downloaded from Google Font

  • The background.png file is the background image for all generated Open Graph images, the size is 1200x630 as per recommendation.

background

  • The logo.png is the logo of the Quick Drop app. It is used for branding on the OG image

logo

  • The template.html the template for the Open Graph image. The
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Custom Font Screenshot</title>
  <style>
    @font-face {
      font-family: 'Inter-Bold';
      src: url('Inter-Bold.ttf') format('truetype');
    }

    @font-face {
      font-family: 'Inter-Regular';
      src: url('Inter-Regular.ttf') format('truetype');
    }

    body {
      font-family: 'Inter-Regular', sans-serif;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      margin: 0;
      background: url('background.jpg') no-repeat center center fixed;
      padding-left: 20px;
      padding-right: 20px;
    }

    .text-regular {
      font-family: 'Inter-Regular', sans-serif;
    }
    .text-bold {
      font-family: 'Inter-Bold', sans-serif;
    }
  </style>
</head>

<body>
  <div style="position: relative; width: 1160px; height: 590px; color: white;">
    <div style="position: absolute; top: 0px; left: 0px; display: flex; align-items: center; opacity: 0.7;">
      <img src="logo.png" width="50" height="50">
      <span class="text-bold" style="font-size: 24px; margin-left: 10px; color: #FF2C55;">Quick Drop</span>
    </div>
    <div
      style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 90px; text-align: center; width: 100%; color: #FF2C55;">
      <span class="text-bold">{{title}}</span>
    </div>
    <div class="text-bold" style="position: absolute; top: 10px; right: 0px; font-size: 24px; opacity: 0.7; color: #FF2C55;">
      https://quickdrop.antran.app
    </div>
  </div>
</body>

</html>
  • The generateOGImages.js is the main javascript file responsible for generating the image based on the input parameters:
    • pageTitle: the title text that will be rendered on the OG image
    • filePath: the path of the output file
const puppeteer = require('puppeteer');
const path = require('path');
const fs = require('fs').promises;

async function generateImage(pageTitle, filePath) {
    const templatePath = path.join(__dirname, 'template.html');
    let html = await fs.readFile(templatePath, 'utf8');
    html = html.replace('{{title}}', pageTitle);

    const tempFilePath = path.join(__dirname, 'temp.html');
    await fs.writeFile(tempFilePath, html);

    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.setViewport({ width: 1200, height: 630 });

    await page.goto(`file:${tempFilePath}`, { waitUntil: 'networkidle0' });

    await page.screenshot({ path: `${filePath}.png` });
    await browser.close();
    await fs.unlink(tempFilePath);
    console.log(`${filePath}.png saved`);
}

module.exports = generateImage;
  • The build.js is used to generate all OG images for all pages
const generateImage = require('./generateOgImages');

async function main() {
  await generateImage("How to compress PDFs for free quickly?", "public/blogs/compress_pdf/og");
}

main().catch(console.error);

Before pushing the site to my remote server, I’ll manually run the following command to generate the OG images for all pages added into the build.js:

node scripts/build.js

The generated Open Graph image should look like this:

open graph image

When you post website to X, the result can be seen from this Tweet:

Quick Drop logo

Profile picture

Personal blog by An Tran. I'm focusing on creating useful apps.
#Swift #Kotlin #Mobile #MachineLearning #Minimalist


© An Tran - 2024