Nextjs 14

Generate Dynamic Open Graph with Next.js 14

One powerful way to enhance your website's shareability on social media platforms is by implementing dynamic Open Graph meta tags. In this comprehensive guide, we'll delve into how you can seamlessly achieve this with Next.js 14, empowering your site to stand out in the crowded digital space.

Exciting Times for Next.js Enthusiasts

Exciting times have arrived for Next.js enthusiasts! The release of Next.js 13 brought stability to the App Router, accompanied by a wave of innovative features and approaches. As developers, staying on the cutting edge is paramount, and this release propels us into a realm of possibilities.

In this series of articles, we explore the new horizons that Next.js 13 unfolds. Today, our focus is on a crucial aspect of web development — SEO. With the introduction of Next.js 13, we bid farewell to the old 'Head.tsx' way of managing SEO. Instead, we embark on a journey to discover a fresh approach that aligns with the latest practices and elevates the way we optimize our websites.

Introduction: Understanding Open Graph

Before we dive into the implementation details, let's understand the significance of Open Graph. Developed by Facebook, Open Graph is an internet protocol that standardizes the incorporation of metadata on a webpage to represent its content. It enables the inclusion of information ranging from the page title to specific details like the duration of a video. Have you ever posted a link on social media?

Open Graph

Slack showing additional data using OG

Implementing Dynamic Open Graph with Next.js

Let's take a practical approach and see how we can implement dynamic Open Graph in Next.js. The following code snippet demonstrates how to generate dynamic Open Graph images based on a post title.

Step 1: Create a new Next.js project

Start by creating a new Next.js project. Open your terminal and run the following command:

bunx create-next-app@latest
bunx create-next-app@latest

Here's how the terminal output should look like:

// Terminal output
 What is your project named?  dynamic-opengraph-demo
 Would you like to use TypeScript?  No / Yes
 Would you like to use ESLint?  No / Yes
 Would you like to use Tailwind CSS?  No / Yes
 Would you like to use `src/` directory?  No / Yes
 Would you like to use App Router? (recommended) … No / Yes
 Would you like to customize the default import alias (@/*)? … No / Yes
 What import alias would you like configured?  @/*
// Terminal output
 What is your project named?  dynamic-opengraph-demo
 Would you like to use TypeScript?  No / Yes
 Would you like to use ESLint?  No / Yes
 Would you like to use Tailwind CSS?  No / Yes
 Would you like to use `src/` directory?  No / Yes
 Would you like to use App Router? (recommended) … No / Yes
 Would you like to customize the default import alias (@/*)? … No / Yes
 What import alias would you like configured?  @/*

And then change directory to the newly created project:

cd dynamic-opengraph-demo
cd dynamic-opengraph-demo

Here's the project structure:

.
├── README.md
├── bun.lockb
├── next-env.d.js
├── next.config.js
├── package.json
├── postcss.config.js
├── public
│   ├── fonts // I created this folder to store my fonts
│   │   └── outfit-semibold.ttf // Here am using outfit font
│   ├── og-bg.png // I created this image to use as background
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── api // I created this folder to store my api routes
│       │   └── og 
│       │       └── route.tsx // Here is my og route
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── tailwind.config.js
└── tsconfig.json
.
├── README.md
├── bun.lockb
├── next-env.d.js
├── next.config.js
├── package.json
├── postcss.config.js
├── public
│   ├── fonts // I created this folder to store my fonts
│   │   └── outfit-semibold.ttf // Here am using outfit font
│   ├── og-bg.png // I created this image to use as background
│   ├── next.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── api // I created this folder to store my api routes
│       │   └── og 
│       │       └── route.tsx // Here is my og route
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       └── page.tsx
├── tailwind.config.js
└── tsconfig.json

As you can see, I created a folder called api to store my api routes. I also created a folder called fonts to store my fonts. You can use any font you want.

Step 2: Implement the Dynamic Open Graph Route

Now, let's delve into the code. Here's how you can implement the dynamic Open Graph route in your Next.js project:

// Import required modules and constants
import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";
 
// Route segment config
export const runtime = "edge";
 
// Define a function to handle GET requests
export async function GET(req: NextRequest) {
  // Extract title from query parameters
  const { searchParams } = req.nextUrl;
  const postTitle = searchParams.get("title");
 
  // Fetch the Outfit font from the specified URL
  const font = fetch(
    new URL("../../../../public/fonts/outfit-semibold.ttf", import.meta.url),
  ).then((res) => res.arrayBuffer());
  const fontData = await font;
 
  // Create an ImageResponse with dynamic content
  return new ImageResponse(
    (
      <div
        style={{
          height: "100%",
          width: "100%",
          display: "flex",
          flexDirection: "column",
          alignItems: "flex-start",
          justifyContent: "center",
          backgroundImage: `url(http://localhost:3000/og-bg.png)`,
        }}
      >
        <div
          style={{
            marginLeft: 190,
            marginRight: 190,
            display: "flex",
            fontSize: 140,
            fontFamily: "Outfit",
            letterSpacing: "-0.05em",
            fontStyle: "normal",
            color: "white",
            lineHeight: "120px",
            whiteSpace: "pre-wrap",
          }}
        >
          {postTitle}
        </div>
      </div>
    ),
    // ImageResponse options
    {
      width: 1920,
      height: 1080,
      fonts: [
        {
          name: "Outfit",
          data: fontData,
          style: "normal",
        },
      ],
    },
  );
}
// Import required modules and constants
import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";
 
// Route segment config
export const runtime = "edge";
 
// Define a function to handle GET requests
export async function GET(req: NextRequest) {
  // Extract title from query parameters
  const { searchParams } = req.nextUrl;
  const postTitle = searchParams.get("title");
 
  // Fetch the Outfit font from the specified URL
  const font = fetch(
    new URL("../../../../public/fonts/outfit-semibold.ttf", import.meta.url),
  ).then((res) => res.arrayBuffer());
  const fontData = await font;
 
  // Create an ImageResponse with dynamic content
  return new ImageResponse(
    (
      <div
        style={{
          height: "100%",
          width: "100%",
          display: "flex",
          flexDirection: "column",
          alignItems: "flex-start",
          justifyContent: "center",
          backgroundImage: `url(http://localhost:3000/og-bg.png)`,
        }}
      >
        <div
          style={{
            marginLeft: 190,
            marginRight: 190,
            display: "flex",
            fontSize: 140,
            fontFamily: "Outfit",
            letterSpacing: "-0.05em",
            fontStyle: "normal",
            color: "white",
            lineHeight: "120px",
            whiteSpace: "pre-wrap",
          }}
        >
          {postTitle}
        </div>
      </div>
    ),
    // ImageResponse options
    {
      width: 1920,
      height: 1080,
      fonts: [
        {
          name: "Outfit",
          data: fontData,
          style: "normal",
        },
      ],
    },
  );
}

Code Explanation

  1. Import Modules and Constants: Import the necessary modules and constants.
  2. Set Runtime: Declare runtime as 'edge'.
  3. Handle GET Requests: Define an asynchronous function to handle GET requests.
  4. Fetch Font Data: Use the fetch API to get the Outfit font data and convert it to an array buffer.
  5. Create ImageResponse: Generate an ImageResponse object with dynamic JSX content.
  6. Set ImageResponse Options: Specify options for the ImageResponse, including width, height, and font details.

Step 3: Test Your Dynamic Open Graph Route

Now, let's test your dynamic Open Graph route. Follow these steps:

  1. Start your Next.js development server with bun run dev
  2. Open your browser and navigate to http://localhost:3000/api/og?title=YourDynamicTitle. Replace "YourDynamicTitle" with the desired title for your Open Graph image.

Step 4: Implement the Dynamic Open Graph Meta Tags

Next14 uses an object called metadata to describe the page’s SEO attributes including OG attributes. (can be placed in layout.js / page.js)

import { Metadata } from 'next'; // if using TypeScript
 
export const metadata: Metadata = {
  openGraph: {
    title: 'Next.js',
    description: 'The React Framework for the Web',
    url: 'https://nextjs.org',
    siteName: 'Next.js',
    images: [
      {
        url: 'http://localhost:3000/api/og?title=Next.js', // Dynamic og route
        width: 800,
        height: 600,
      },
      {
        url: 'http://localhost:3000/api/og?title=Next.js', // Dynamic og route
        width: 1800,
        height: 1600,
        alt: 'My custom alt',
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
};
 
 
export default function Page() {}
import { Metadata } from 'next'; // if using TypeScript
 
export const metadata: Metadata = {
  openGraph: {
    title: 'Next.js',
    description: 'The React Framework for the Web',
    url: 'https://nextjs.org',
    siteName: 'Next.js',
    images: [
      {
        url: 'http://localhost:3000/api/og?title=Next.js', // Dynamic og route
        width: 800,
        height: 600,
      },
      {
        url: 'http://localhost:3000/api/og?title=Next.js', // Dynamic og route
        width: 1800,
        height: 1600,
        alt: 'My custom alt',
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
};
 
 
export default function Page() {}

the above code will generate the following meta tags:

<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:url" content="https://nextjs.org/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image:url" content="http://localhost:3000/api/og?title=Next.js" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image:url" content="http://localhost:3000/api/og?title=Next.js" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="My custom alt" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:url" content="https://nextjs.org/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image:url" content="http://localhost:3000/api/og?title=Next.js" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image:url" content="http://localhost:3000/api/og?title=Next.js" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="My custom alt" />
<meta property="og:type" content="website" />

Conclusion

With these steps, you've successfully implemented and tested dynamic Open Graph in your Next.js project. This feature enhances your website's shareability on social media platforms and provides a visually appealing representation of your content.

For more information, check out the official documentation on Open Graph or check out on my Github.