Understand Open Graph ( OG ) in Next Js : A Practical Guide

October 202412 min read
Understand Open Graph ( OG ) in Next Js : A Practical Guide

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 Preview on social media platforms

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 :

  • Creating a Next Js project npx create-next-app@latest
  • Installing OGraph Previewer Chrome extension, a must-have tool when working with Open Graph (OG). It offers a quick and easy way to visualize how your web pages will appear when shared on social media platforms. Simply install the extension and visit any page on your website. With a single click, you’ll be able to see a preview of your page’s title, description, and image, ensuring that they align with your desired presentation.

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>
  );
};
  • Update app/page.tsx to :
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.

1. Implementing Open Graph using an Image file.

Let’s add this image in public directory, and export it in src/lib/Resources.ts

OG Image

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 :

  1. title and description: These repeat the title and description from the outer level, ensuring consistency.
  2. url: This property sets the URL of the current page.
  3. images is an array of image objects for the Open Graph :
  • url: The URL of the image.
  • secureUrl: The secure URL of the image (e.g., HTTPS).
  • width and height: These properties provide the dimensions of the image.
  • alt: The alternative text for the image.
  1. type: Specifies the type of content on your page. Common values include “website”, “article”, “book”, and “profile”.

  2. 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

Image Preview

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" /> 

2. Implementing Open Graph using a generated Image.

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 :

  • A new ImageResponse is created, which represents the generated image.
  • The div element inside the ImageResponse defines the content of the image. It includes a heading with the blog title and a background gradient.
  • The fonts property specifies the font used in the image. The rubik font data is included in the data property.

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

Image Preview

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!

3. Implement Dynamic Open Graph Route.

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.

Image description

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” )

Image Preview

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.

Image description

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 😎

Image preview

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

GitHub repo


☞ Follow me on Twitter & Linkedin

☞ Kindly subscribe for my upcoming articles