Imagine a world where your content effortlessly shares itself, captivating audiences on social media. This is the reality made possible by Open Graph (OG) protocol. In the realm of Next.js, the use of OG becomes even more strategic.
Open Graph is a protocol that allows websites to provide rich snippets of information about their content when shared on social media platforms. By implementing Open Graph image, you can control how your content appears when shared on Facebook, Twitter, LinkedIn, and other platforms. This ensures that your posts are visually appealing and informative, encouraging users to click and engage with your content.
In this guide, we’ll explore how to generate OG images dynamically and cover different implementation scenarios, tailoring OG tags to match your specific content requirements. Ensuring your content looks great when shared on social media platforms.
Assuming you have a solid understanding of Next.js and its core concepts, and familiarity with Tailwind CSS for styling Open Graph, Let’s start by :
Before we delve into the intricacies of Open Graph tags in Next.js, let’s set the stage with a clean and well-structured project.
Remove the app/fonts files since we won’t be needed them for our blog setup.
Update app/layout.tsx to :
import "./globals.css";
import type { Metadata } from "next";
import { Poppins } from "next/font/google";
const poppins = Poppins({
subsets: ["latin"],
display: "swap",
weight: ["300", "400", "500", "600", "700"],
variable: "--poppins",
});
const baseUrl = "https://www.danmugh.com/"; // Your website url
export async function generateMetadata(): Promise<Metadata> {
const title = "My Blog";
const description =
"Welcome to my blog! Dive into a wealth of insightful articles, practical guides, and project breakdowns, empowering you to level up your Rust and Blockchain development skills.";
return {
metadataBase: new URL(baseUrl),
title,
description,
themeColor: "black"
};
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${poppins.className} ${poppins.variable} font-sans bg-dark`}
>
<div className="w-full min-h-screen flex flex-col sm:px-[100px] md:px-[140px] lg:px-[200px] xl:px-[225px]">
{children}
</div>
</body>
</html>
);
};
import React from "react";
export default function Home() {
return (
<div className="w-full min-h-screen pt-24 ">
<div className="flex flex-col justify-center items-center">
<h1 className="text-white text-3xl sm:text-5xl text-center font-semibold">
Blog Page
</h1>
<p className="w-[70%] mt-8 mb-3 text-lg text-white/50 text-center font-medium leading-normal">
Welcome to my blog! Dive into a wealth of insightful articles,
practical guides, and project breakdowns, empowering you to level up
your Rust and Blockchain development skills.
</p>
</div>
</div>
);
}
Delete all the files in public directory.
Create in src, components and lib directories
In src/lib, create Resources.ts file
Create in src, app/api/og directory
Create in public, fonts directory and add rubik.ttf
We should have this structure :
.
├── public
│ └── fonts
│ └── rubik.ttf
├── src
│ ├── app
│ │ ├── api
│ │ │ └── og
│ │ │ └── route.tsx
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── components
│ └── lib
│ └── Resources.ts
│
├── tailwind.config.ts
├── tsconfig.json
├── README.md
├── next-env.d.ts
├── next.config.mjs
├── package.json
└── postcss.config.js
Now that we’ve set up a basic blog structure in place, we’re ready to add content and implement Open Graph image.
Let’s add this image in public directory, and export it in src/lib/Resources.ts
export const Thumbnail = '/thumbnail.png'; // Assuming the image name is thumbnail
Change the layout metadata to :
export async function generateMetadata(): Promise<Metadata> {
const title = "My Blog";
const description =
"Welcome to my blog! Dive into a wealth of insightful articles, practical guides, and project breakdowns, empowering you to level up your Rust and Blockchain development skills.";
return {
metadataBase: new URL(baseUrl),
title,
description,
themeColor: "black",
openGraph: {
title,
description,
url: baseUrl,
images: [
{
url: Thumbnail,
secureUrl: Thumbnail,
width: 1200,
height: 630,
alt: "Preview image for Dan Mugh's Blog",
},
],
type: "website",
siteName: "Dan Mugh's Blog",
},
};
}
const title and description : Respectively set the title and the description of the blog page and the Open Graph title & description. metadataBase : Specifies the base URL of the website ( https://www.danmugh.com in our case ). themeColor : The preferred color for the browser’s UI. openGraph is an object containing Open Graph metadata :
type: Specifies the type of content on your page. Common values include “website”, “article”, “book”, and “profile”.
siteName: The name of the website.
Note that the flexibility of having multiple image objects in images array, is particularly useful for catering to different social media platforms and their specific image size requirements. For example, Twitter might prefer a smaller square image (800x800), while Facebook might favor a larger landscape format (1800x1600). By including multiple image objects with appropriate dimensions, you ensure that your content is displayed optimally on various platforms, maximizing its visual impact and potential reach.
After adding these changes, we should be able to test from OGraph Previewer and get this output
And having the following meta tags as output :
<meta property="og:title" content="My Blog" />
<meta property="og:description" content="Dive into a wealth of insightful articles..." />
<meta property="og:url" content="https://www.danmugh.com/" />
<meta property="og:site_name" content="Dan Mugh's Blog" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="http://localhost:3000/thumbnail.png">
<meta property="og:image:alt" content="Preview image for Dan Mugh's Blog" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:type" content="website" />
The easiest way to generate an image is to use the ImageResponse API from next/og.
Let’s create route.tsx in app/api/og and add the below code :
import { ImageResponse } from "next/og";
// Route segment config
export const runtime = "edge";
export const contentType = "image/png";
// Image generation
export async function GET() {
try {
// Font
const rubik = await fetch(
new URL("../../../../public/fonts/rubik.ttf", import.meta.url)
).then((res) => res.arrayBuffer());
return new ImageResponse(
(
<div
style={{
fontFamily: "Rubik",
fontWeight: "bolder",
background: "linear-gradient(to right, #6a11cb 0%, #2575fc 100%)",
}}
tw="w-full h-full p-8 flex items-center justify-center relative"
>
<div tw="flex flex-col">
<h1 tw="text-white text-5xl font-bold leading-[4px]">Learn more today!</h1>
<h1 tw="text-white/70 text-3xl font-bold leading-[8px]">Dive into the future of tech...</h1>
</div>
</div>
),
{
fonts: [
{
name: "Rubik",
data: rubik,
weight: 700,
style: "normal",
},
],
}
);
} catch (e: any) {
return new Response("Failed to generate OG Image", { status: 500 });
}
}
runtime Property : This property specifies that the code should be executed on the edge, which is a serverless environment optimized for serving static content
alt, size, and contentType Properties : These properties define the alternative text, dimensions, and content type of the image, respectively. You can set the size and alt from the openGraph in the layout file as well.
GET Function : This function handles HTTP GET requests to the route.
Font Loading : The code fetches the rubik.ttf font file and converts it to an array buffer. This is necessary for including the font in the image generation process ( You may need to adjust the font path and file name (rubik.ttf) to match your project structure ).
Image Creation :
Error Handling : A try-catch block is used to handle potential errors during the font fetching and image generation process. If an error occurs, a response with a 500 status code is returned.
You can effectively combine Tailwind CSS classes with custom CSS for a more flexible approach. And I will recommend to use Tailwind classes ( tw ) for common styles and custom CSS for more specific or complex requirements.
Additionally, we can update layout openGraph to :
…
openGraph: {
title,
description,
url: baseUrl,
images: [
{
url: `/api/og`,
width: 1200,
height: 630,
alt: "Preview image for Dan Mugh's Blog",
},
],
locale: "en_US",
type: "website",
siteName: "Dan Mugh's Blog",
},
By now, we should be able to see the output in the OGraph Previewer, or by visiting http://localhost:3000/api/og
And have this output :
<meta property="og:title" content="My Blog">
<meta property="og:description" content="Dive into a wealth of insightful articles, practical guides, and project breakdowns, empowering you to level up your Rust and Blockchain development skills.">
<meta property="og:url" content="https://www.danmugh.com">
<meta property="og:site_name" content="Dan Mugh's Blog">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="http://localhost:3000/api/og">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Preview image for Dan Mugh's Blog">
<meta property="og:type" content="website">
While we’ve covered core concepts for creating Open Graph image, you can explore Vercel Image Playground for interactive experimentation. This handy tool lets you visually build and test Open Graph images by directly editing the HTML and CSS that define their appearance. You can play with fonts, colors, layouts, and even preview the generated image in real-time.
Once you’ve mastered the basics here, head over to the Vercel Image Playground to solidify your Open Graph image creation skills!
To make our blog truly dynamic and showcase fresh content, we need to integrate an API to fetch articles. Here, we’ll leverage the dev.to API, which provides access to published articles on the platform. The API endpoint we’ll use is :
https://dev.to/api/articles/{username}
If you don’t have a dev.to account yet, no worries! You can simply use mine (danmugh) for demonstration purposes. ( But make sure to replace it with your username later when you create your own account 😄 ).
I’ve made some significant changes to the project, to fetch and display blog articles. I won’t delve into the details here. Feel free to clone the repository and update your project to incorporate these enhancements.
Assuming you’ve successfully updated the project and you’re seeing the blog content locally, let’s go to article/[slug] and change the metadata to :
export async function generateMetadata({
params: { slug },
}: Props): Promise<Metadata> {
const baseUrl = "https://www.danmugh.com/";
const article = (await serverServices.getArticle(
slug
)) as IArticleModel;
const title = article.title;
const description = article.description;
const articleSlug = article.slug;
return {
title,
description,
metadataBase: new URL(baseUrl),
openGraph: {
title,
description,
images: [
{
url: `/api/og?title=${articleSlug}`,
width: 1200,
height: 630,
alt: `Preview image for ${title}`,
},
],
type: "article",
url: `/blog/${slug}`,
},
alternates: {
canonical: `/blog/${slug}`,
},
};
}
We are passing the article slug to the Open Graph image, so we can fetch the article data from the route.
In api/og/route.tsx, above font loading, we can add :
...
const article: IArticleModel = await fetch(
`https://dev.to/api/articles/danmugh/${req.nextUrl.searchParams.get(
"title"
)}`
).then((res) => res.json());
Now that we have the article data in the route, we can use the data to design the Open Graph Image.
...
return new ImageResponse(
(
<div
style={{
fontFamily: "Rubik",
fontWeight: "bolder",
background: "linear-gradient(to right, #6a11cb 0%, #2575fc 100%)",
}}
tw="w-full h-full p-8 flex items-center justify-center relative"
>
<div tw="w-full flex flex-col">
<h1 tw="text-[36px] text-white font-bold">{article.title}</h1>
<div tw="h-[50px] mt-3 flex justify-between items-center">
<div tw="flex justify-center items-center">
<img
src={article.user.profile_image}
width={100}
height={100}
alt={article.title}
tw="w-[60px] h-[60px] rounded-[10px] mr-2"
style={{ objectFit: "cover" }}
/>
<div tw="flex flex-col">
<p tw="text-[20px] text-white font-semibold leading-[1px]">
{article.user.name}
</p>
<p tw="text-[20px] text-white/60 font-semibold leading-[1px]">
Author
</p>
</div>
</div>
<p tw="text-[20px] text-white font-semibold">
{article.reading_time_minutes} Min read
</p>
</div>
</div>
</div>
),
{
fonts: [
{
name: "Rubik",
data: rubik,
weight: 700,
style: "normal",
},
],
}
);
We should have a preview of the OG image from the OGraph Previewer extension.
Or by visiting http://localhost:3000/api/og?title=${articleSlug} ( Assuming the articleSlug is “integrating-wagmi-v2-and-rainbowkit-in-nextjs-a-comprehensive-guide-part-1–37ck” )
And this output as well :
<meta property="og:title" content="Integrating Wagmi V2 and Rainbowkit in NextJs : A Comprehensive Guide (Part 1)">
<meta property="og:description" content="Welcome to the forefront of Web3/Frontend development, where we embark on a transformative journey of...">
<meta property="og:url" content="https://www.danmugh.com/blog/integrating-wagmi-v2-and-rainbowkit-in-nextjs-a-comprehensive-guide-part-1-37ck">
<meta property="og:image" content="http://localhost:3000/api/og?title=integrating-wagmi-v2-and-rainbowkit-in-nextjs-a-comprehensive-guide-part-1-37ck">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Preview image for Integrating Wagmi V2 and Rainbowkit in NextJs : A Comprehensive Guide (Part 1)">
<meta property="og:type" content="article">
Now that we’ve explored various Open Graph implementation scenarios, let’s wrap up this guide with a more intricate case ( a truly mind-bending case study ), where we’ll push the boundaries of Open Graph image customization to the absolute limit. Buckle up, folks 😎
In this section, we’ll delve into advanced techniques for customizing OG images, allowing you to create truly unique and visually striking previews for your content.
Let’s add this image in public and export it from lib/Resource.ts. We shall use it as background of the OG image we are going to build.
export const OGBackground = '/OGBackground.png'; // Assuming the image name is OGBackground
In route.tsx, we can fetch the background image file and passe it as an ArrayBuffer :
const bg = await fetch(
new URL("../../../../public/OGBackground.png", import.meta.url)
).then((res) => res.arrayBuffer());
And change the ImageResponse to :
<div
style={{
fontFamily: "Rubik",
fontWeight: "bolder",
}}
tw="w-full h-full flex items-center justify-center relative"
>
<img
src={bg}
width={100}
height={100}
alt="background"
tw="w-full h-full"
style={{ objectFit: "cover" }}
/>
<div tw="absolute top-0 left-0 p-16 w-full h-full flex items-center justify-start">
<div
style={{ background: "#1B2022" }}
tw="max-w-[420px] h-full mx-0 p-5 shadow-[0px_8px_16px_0px_rgba(0,0,0,0.15)] rounded-xl flex flex-col items-start justify-between"
>
<div tw="flex flex-col">
<div tw="flex justify-center items-center">
<img
src={article.cover_image}
tw="w-full h-[190px]"
width={100}
height={100}
/>
</div>
<h2 tw="mt-4 text-2xl text-white text-start font-bold leading-8 line-clamp-3">
{article.title}
</h2>
<p tw="text-lg text-white/50 text-start font-normal leading-6 line-clamp-5">
{article.description}
</p>
</div>
<div tw="w-full flex justify-between items-center">
<p tw="text-[17px] text-white/75 font-semibold">
{article.readable_publish_date}
</p>
<p tw="text-[17px] text-white/75 font-semibold">
{article.reading_time_minutes} Min read
</p>
</div>
</div>
</div>
<div tw="absolute top-0 right-0 p-16 w-full h-full flex items-end justify-end">
<div tw="flex justify-center items-center">
<p tw="text-[22px] text-white/85 font-semibold leading-[1px]">
#LearnMoreToday
</p>
<img
src={article.user.profile_image}
width={100}
height={100}
alt={article.title}
tw="w-[40px] h-[40px] rounded-[10px] ml-3"
style={{ objectFit: "cover" }}
/>
</div>
</div>
</div>
By combining Tailwind CSS classes with custom CSS, we have created a highly customized, unique and visually appealing OG Image.
And here we are 😎
In this guide, we’ve explored the essential concepts of Open Graph metadata in Next.js. By understanding and implementing Open Graph, you can significantly enhance your website’s visibility and engagement on social media platforms. From basic setup to advanced customization, we’ve covered various techniques to create compelling and informative social media previews. Remember, well-crafted Open Graph metadata is an investment in your website’s online presence.
Now that you’ve acquired some Open Graph skills, it’s time to unleash your creativities. I’d be delighted to see the visually appealing Open Graph Images you create!
Feel free to share your website links with me at contact@danmugh.com
☞ Follow me on Twitter & Linkedin
☞ Kindly subscribe for my upcoming articles