Automating open graph images with NextJS 14, TailwindCSS and Typescript | Rob Cipolla

Automating open graph images with NextJS 14, TailwindCSS and Typescript

Posted: May 01, 2024 Estimated reading time: 15 mins

Learn how to automate the generation of open graph images for your blog posts using NextJS 14, TailwindCSS and Typescript

Contents

Video

TLDR; Just give me the code

The code for this tutorial and example can be found on GitHub

Introduction

There are a lot of steps to creating a successful blog, but if you really want yours to stand out, you need to make sure it looks good when shared on social media. One way to do this is by creating custom open graph images for each of your blog posts. This can be a time-consuming process, but with the right tools, you can automate it and save yourself a lot of time.

In this post, I’ll show you how to automate the generation of open graph images for your blog posts using NextJS 14, TailwindCSS and Typescript. The key ingredient here will be Vercel’s og library which you can read more about here @vercel/og.

Creating a new NextJS project

The first thing you’ll need to do is create a new NextJS project. You can do this by running the following command:

npx create-next-app@latest my-blog

Simply follow the prompts and you’ll have a new NextJS project up and running in no time. For this project we are using the src directory as the root of our project and TypeScript so be sure to select those when prompted if you want to follow along.

This guide is also using the app directory from NextJS. The steps for this are very similar if you want to use the pages directory, however you should check the documention for more details.

Creating a static open graph image

To start we are going to create a static image for our home page, this will be the image that is displayed when our site’s homepage is shared on social media.

As we are using the app directory, start by creating a new folder inside the app folder called og. Inside this folder create a new folder called home.png (yes a folder, and make sure to include the file extension). Then inside here we are going to create a route handler that will generate our image. So create a new file called route.tsx making sure to include the .tsx extension (normally you wouldn’t need to have .tsx but soon we will be writing some JSX in our handler).

Your file directory should now look like this:

app/
  og/
    home.png/
      route.tsx

Inside route.tsx we are going to utilise the ImageResponse class from the @vercel/og library to generate our image. This class takes two arguments, the first is the JSX that will be rendered to the image and the second is an object with the width and height of the image.

One thing to note about the Tailwind classes which are used in the JSX is that they are not added within the className attribute as you would normally do in a React component. This is because the @vercel/og library adds a tw prop to the JSX which allows you to add Tailwind classes directly to the elements. Not a big deal but something to be aware of.

The code below does the following:

  • Imports the ImageResponse class from the @vercel/og library
  • Creates and exports a GET handler function that returns a new ImageResponse object
  • The ImageResponse object takes two arguments, the first is the JSX that will be rendered to the image and the second is an object with the width and height of the image
import { ImageResponse } from "next/og";

export async function GET() {
  return new ImageResponse(
    (
      <div tw="h-[630px] w-[1200px] flex bg-blue-500 text-white text-5xl items-center justify-center">
        My fancy homepage open graph image
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
}

Now if you run your NextJS app and navigate to http://localhost:3000/og/home.png you should see your image being generated. Naturally you will want to add more styles and content to your image and you can do so by adding more Tailwind classes and JSX to the ImageResponse object.

Generating open graph images for blog posts

Generating a single image is great and all… but you don’t want to have to create a new image for every blog post you write. Instead, you can create a dynamic route that will generate an image for each of your blog posts.

First let’s start by setting up our blog route. Inside the app folder create a new folder called blog. Inside this folder create a new folder called [slug] and inside this folder create a new file called page.tsx.

Your file directory should now look like this:

app/
  blog/
    [slug]/
      page.tsx

As this is not a guide on how to build a blog with NextJS, I am going to create an extremly simple blog page component in the page.tsx file like so:

export default function Page({ params }: { params: { slug: string } }) {
  return (
    <div>
      <h1>Blog Post - {params.slug}</h1>
    </div>
  );
}

All this component does is render a heading with the slug of the blog post. Now we need to create a new route handler that will generate an image for this blog post. Inside the [slug] folder create a new directory called og followed by [slug].png and inside here create a new file called route.tsx.

Your file directory should now look like this:

app/
  blog/
    [slug]/
      og/
        [slug].png/
          route.tsx

Inside route.tsx we are going to use the same ImageResponse class from the @vercel/og library to generate our image. However we are also going to extract the slug from the URL and use it to generate a title for our blog post’s image.

The code below does the following:

  • Imports the ImageResponse class from the @vercel/og library
  • Creates and exports a GET handler function that returns a new ImageResponse object
  • Extracts the slug from the URL and removes the .png extension
  • Removes dashes from the slug and capitalizes the first letter of each word
  • Returns a new ImageResponse object with the title of the blog post as the content of the image
import { ImageResponse } from "next/og";

export async function GET(request: Request) {
  const url = new URL(request.url);

  // get the slug from the URL and remove .png
  let slug = url.pathname.split("/").pop();
  slug = slug?.split(".")[0];

  // remove dashes from the slug and capitalize the first letter of each word
  slug = slug?.replace(/-/g, " ");
  slug = slug?.replace(/\b\w/g, (char) => char.toUpperCase());

  return new ImageResponse(
    (
      <div tw="h-[630px] w-[1200px] flex bg-blue-500 text-white text-5xl items-center justify-center">
        {slug}
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
}

Now if you run your NextJS app and navigate to http://localhost:3000/blog/my-blog-post.png you should see your image being generated with the title of your blog post. Again, you will want to add more styles and content to your image and you can do so by adding more Tailwind classes and JSX to the ImageResponse object.

Next you will need to make sure the open graph image is included in the meta tags of your blog post.

To do this we will be re-using the code we used in our route.tsx file to generate the title. So I would recommended creating a new directory called lib or utils inside the src directory and creating a new file called something like slug.ts and adding the following code:

export function slugToTitle(slug: string) {
  // remove dashes from the slug and capitalize the first letter of each word
  slug = slug.replace(/-/g, " ");
  slug = slug.replace(/\b\w/g, (char) => char.toUpperCase());

  return slug;
}

We can then refactor the code in our route.tsx file to use this function like so:

import { slugToTitle } from "@/utils/slug";
import { ImageResponse } from "next/og";

export async function GET(request: Request) {
  const url = new URL(request.url);

  let slug = url.pathname.split("/").pop();
  slug = slug?.split(".")[0];

  if (slug === undefined) {
    return new Response("Not found", { status: 404 });
  }

  const title = slugToTitle(slug);

  return new ImageResponse(
    (
      <div tw="h-[630px] w-[1200px] flex bg-blue-500 text-white text-5xl items-center justify-center">
        {title}
      </div>
    ),
    {
      width: 1200,
      height: 630,
    }
  );
}

Once we have our new function we can add our open graph image to the meta tags of our blog posts using the generateMetaData function from NextJS.

In the code below for the page.tsx file in the [slug] folder we are doing the following:

  • Importing the slugToTitle function from the slug.ts file
  • Creating a new generateMetadata function that takes a params object and a searchParams object
  • Using the slugToTitle function to generate a title for the blog post
  • Returning a new Metadata object with the title of the blog post and an image object with the URL of the image and its width and height
import { slugToTitle } from "@/utils/slug";
import type { Metadata } from "next";

type Props = {
  params: { slug: string };
  searchParams: { [key: string]: string | string[] | undefined };
};

export function generateMetadata({ params, searchParams }: Props): Metadata {
  return {
    title: slugToTitle(params.slug),
    openGraph: {
      images: [
        {
          url: `https://example.com/blog/${params.slug}/og/${params.slug}.png`,
          width: 1200,
          height: 630,
        },
      ],
    },
  };
}

export default function Page({ params }: { params: { slug: string } }) {
  return (
    <div>
      <h1>Blog Post - {params.slug}</h1>
    </div>
  );
}

And there we have it. You now have a way to automate the generation of open graph images for your blog posts using NextJS 14, TailwindCSS and Typescript. This will save you a lot of time and make your blog posts look great when shared on social media.

Subscribe to my newsletter