Contents
- Video
- TLDR; Just give me the code
- Introduction
- Creating a new NextJS project
- Creating a static open graph image
- Generating open graph images for blog posts
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 newImageResponse
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 newImageResponse
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 theslug.ts
file - Creating a new
generateMetadata
function that takes aparams
object and asearchParams
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.