Creating opengraph images in Next.js 14
Opengraph is amazing making the links to your website look great and professional, enhancing the maturity of your product (or whatever it is). However, there is not enough documentation around how to do it. I wanted to add some dynamic, styles opengraph images and as easy as it is, there aren't enough examples on the web, and Claude 3.5 Sonnet as well as o1-preview (most capable AI models at the time I was writing this blog) struggled doing it. So here is a short tutorial.
- Create a app/og/route.tsx folder inside your app. 2. Understanding and Implementing Dynamic OG Images
In Next.js 14+, the app/og/route.tsx file creates an API endpoint at /og that generates dynamic Open Graph images. This is great for creating custom social media preview images for your content. Personally, I am a lot more likely to click on a link that has an opengraph preview.
To use this as the main image for your website's shared links, add the following to your root layout or page metadata:
// In app/layout.tsx or app/page.tsx
export const metadata = {
openGraph: {
images: [
{
// Or you can use an environment variable to determine the url (probably a better practice)
url: "https://yourdomain.com/og?title=Your Website Title",
},
],
},
twitter: {
card: "summary_large_image",
images: ["https://yourdomain.com/og?title=Your Website Title"],
},
};
Here's the commented code explaining how the OG image generation works:
import { ImageResponse } from "@vercel/og"; // Imports Vercel's image generation component
import { NextRequest } from "next/server"; // Imports Next.js request type
export const runtime = "edge"; // Specifies this should run on the edge runtime for better performance
export async function GET(req: NextRequest) {
// Extracts the title parameter from the URL query string
const { searchParams } = req.nextUrl;
const postTitle = searchParams.get("title");
// Loads the custom font file and convert it to array buffer (optional)
const font = fetch(
new URL("../../public/fonts/kaisei-tokumin-bold.ttf", import.meta.url)
).then((res) => res.arrayBuffer());
const fontData = await font;
// Generates and return the image using JSX-like syntax
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
backgroundImage: "url(https://filipwojda.dev/og-bg.png)", // Custom background image
}}
>
<div
style={{
marginLeft: 190,
marginRight: 190,
display: "flex",
fontSize: 130,
fontFamily: "Kaisei Tokumin", // Use the custom font (optional)
letterSpacing: "-0.05em",
fontStyle: "normal",
color: "white",
lineHeight: "120px",
whiteSpace: "pre-wrap",
}}
>
{postTitle} // Display the dynamic title from URL parameters
</div>
</div>
),
{
width: 1920, // Set output image dimensions to 1920x1080 (16:9 ratio)
height: 1080,
fonts: [
{
name: "Kaisei Tokumin", // Configure the custom font
data: fontData,
style: "normal",
},
],
}
);
}
This will generate a custom image whenever someone shares your website or blog posts on social media platforms. The image will include the title parameter you pass in the URL, styled with your custom font and background.
3. Twitter (X) opengraph cache not refreshing
If you just want the TLDR:
1. Check that the size of your image is 1200 x 630 and ideally below 1 megabyte (not sure what the exact limit is as it is not mentioned anywhere - below this number has worked for me)
2. Change the name of the route of the URL you are trying to share to make Twitter think this is a new route
Opengraph is very easy. Define a route, style the component and we're good. However, my biggest struggle with opengraph images was X's cache. Once upon a time I was asked to create an opengraph image for an important URL linking to Julius' blog for a Founding Product Designer role. The "hardcoded" .png format image and specifying it inside of the individual route's metadata took 2 minutes. However, I committed a few mistakes that are barely mentioned anywhere on the web:
Posted the URL beforehand to see how it looks currently. Twitter cached the image related to the URL.
Did not check the size of the image. No one cares about opengraph image sizes - except for Twitter. I had a 9000(ish) by 7000(ish) resolution and a total size of about 7 megabytes. Turns out opengraph image won't work with Twitter above a certain size. Reducing it to the standard opengraph size of 1,200 x 630 did the trick.
I spent 4 hours trying to hunt down why was it not updating on Twitter. I redeployed the site multiple times after reducing the size of the image. The image finally worked on my dev deployment. But production still did not show an image. The final boss was to invalidate the cache by renaming my route to something slightly different to create a new cache for a new URL.