In this article, we will add OpenGraph to a new Next.js website, so it's easy to grab the knowledge that will enable you to add it to your website and adapt it to your or your client's situation.

We will start as usual by creating a new Next.js project, I like to use typescript on all of my projects. To create a new project I will use this command npx create-next-app@latest --typescript open_graph.

After it's done go and open the project inside your favorite code editor mine is vscode, then start the dev server with npm run dev and check it inside the browser you should see the default Next.js home page.

About OpenGraph

Before getting started with creating a component to add the open graph meta tags let's first learn a little bit about OpenGraph and its properties which we'll be adding to our Next.js website.

What is OpenGraph?

OpenGraph is used to make web pages rich when shared on social media, it enables you to control how it looks. Like page title, preview image, url, and so on.

When a page doesn't include the OpenGraph protocol and it's shared for example on Facebook, Facebook will try to pick some default values like the first image on that page, the page description, etc.

What are OpenGraph meta tags?

To be able to control the preview displayed information when our web page is shared on social, we use meta tags to set those values.

Because this article is about the implementation of Open Graph with Next.js? you can read more here: Open Graph Meta Tags: Everything You Need to Know.

OpenGraph Next.js project

At the end of this article, you will have an OpenGraph component and a useOpenGraph hook that can be reused on multiple pages, or extended by extra meta tags.

To make it easy for you to understand how to reuse it in multiple situations I like to work with a scenario for the rest of the article.

We will have two pages, one is a dynamic posts detail page at /posts/{id}, and another is the home page at /.

For our dynamic page, we will use a simple JSON file with 3 posts in it, then we will use getServerSideProps to read that file.

It won't be complicated we will simply import the JSON file, search for the right post, then show the page with that post's content.

But our mean focus won't be on this part, as you may already have a similar setup, but it will help us understand better the OpenGraph component and its helper hook on how it can be used in multiple types of Next.js pages.

Adding show post page

As mentioned before, let's start by creating a new file at data/posts.json with 3 simple posts, remember this step is only to have something to work with.

data/posts.json
Json
[
  {
    "id": 1,
    "title": "Post 1",
    "thumbnail": "/images/og-posts-image.jpg",
    "description": "Description of post 1",
    "content": "Content of post 1"
  },
  {
    "id": 2,
    "title": "Post 2",
    "thumbnail": "/images/og-posts-image.jpg",
    "description": "Description of post 2",
    "content": "Content of post 2"
  },
  {
    "id": 3,
    "title": "Post 3",
    "thumbnail": "/images/og-posts-image.jpg",
    "description": "Description of post 3",
    "content": "Content of post 3"
  }
]

A post will have an id, title, thumbnail, description, and a content property, these are commonly used fields, and we will use them to set the appropriate Open Graph meta tags.

Don't forget to add a placeholder image at public/images/og-posts-image.jpg as we used it inside out posts data.

Now that we have the posts data we'll need inside the show post page, let's create another new file at pages/posts/[id].tsx with a default Next.js page component.

pages/posts/[id].tsx
TypeScript
import Head from "next/head";
import Image from "next/image";
import { GetServerSideProps, NextPage } from "next";

type Post = {
  id: number;
  title: string;
  thumbnail: string;
  description: string;
  content: string;
};

interface ServerSideProps {
  post: Post;
}

const ShowPost: NextPage<ServerSideProps> = ({ post }) => {
  return (
    <>
      <Head>
        <title>{post.title}</title>
      </Head>

      <main>
        <article>
          <h1>{post.title}</h1>

          <Image
            width={720}
            height={420}
            src={post.thumbnail}
            alt={post.title}
          />

          <div dangerouslySetInnerHTML={{ __html: post.content }}></div>
        </article>
      </main>
    </>
  );
};

export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({
  params,
}) => {
  if (!params || typeof params.id !== "string" || isNaN(+params.id)) {
    return { notFound: true };
  }

  const posts: Post[] = require("../../data/posts.json");

  const postId = +params.id;
  const post = posts.find((p) => p.id === postId);
  if (!post) {
    return { notFound: true };
  }

  return {
    props: {
      post,
    },
  };
};

export default ShowPost;

This is also a common situation, you have some kind of data source, then you use it to retrieve the post data if it was found and 404 otherwise.

First, we declared a Post type, ServerSideProps that uses the Post type and passed a generic type value to both NextPage, and GetServerSideProps types.

Doing that will help our ShowPost component, and getServerSideProps function understands each other, so both returned props and received ones are aligned.

Inside the ShowPost component we accept a post prop and use it to show posts data, the usual stuff.

Inside the getServerSideProps function we first validate the provided id and make sure it's a valid number.

Then we import our JSON data file using the require keyword inside the getServerSideProps to avoid any client/server ssr errors.

Then we convert the postId to a number by adding a plus sign at the beginning, we store it in another variable to help TypeScript understand its type.

Now that we have both a valid ID and a list of posts, we search for the requested post, if it wasn't found we return a 404 response otherwise we return the post prop to the page component.

Simplify the home page

When creating a new Next.js project, by default it comes with a home page with some content in it, that we will clean up to make it easy for us to not get lost outside our context.

We will remove any style-related stuff, classes, and the related CSS module file, go on and open the pages/index.tsx file, and replace it with the following.

pages/index.tsx
TypeScript
import type { NextPage } from "next";
import Head from "next/head";

const Home: NextPage = () => {
  return (
    <div>
      <Head>
        <title>OpenGraph with Next.js</title>
        <meta
          name="description"
          content="Article about OpenGraph with Next.js"
        />
      </Head>

      <main>
        <h1>OpenGraph with Next.js</h1>
        <div>An empty page is a good page</div>
      </main>
    </div>
  );
};

export default Home;

Building the OpenGraph component with Next.js

Now that we have a better idea about Open Graph and that we did set up our Next.js project to help us achieve our goal from this article, it's time to build the actual component.

Go ahead and create a new file at components/common/OpenGraph.tsx as usual we always need to think about the project structure from the start, that's why we put it inside a common directory.

components/common/OpenGraph.tsx
TypeScript
export type OGProperties = {
  locale: "en_US" | "fr_FR"; // https://saimana.com/list-of-country-locale-code/
};

const OpenGraph = ({ properties }: { properties: OGProperties }) => {
  const { locale } = properties;

  return (
    <>
      <meta property="og:locale" content={locale} />
    </>
  );
};

export default OpenGraph;

We will go step-by-step, we declared an OGProperties type and then used it to type the properties prop, as the first step we have a locale as a property.

Open Graph locale property is for telling the page locale, there's a link to all the available locales you can check to find yours.

The OpenGraph component's main purpose is to group a list of OG meta tags, then give them their values from the properties prop that it receives.

We made it a component in the first place because we will need it on multiple pages, and each page may have a different source of data, for example in a multi-language website we can have different locales for different pages.

In the same way, we added the og:locale meta tag we will add the rest of the needed og:* tags.

components/common/OpenGraph.tsx
TypeScript
export type OGProperties = {
  locale?: "en_US" | "fr_FR";
  url: string;
  title: string;
  type: "article" | "website";
  description: string;
  site_name: string;
  image: {
    alt: string;
    type: string;
    url: string;
    width: string;
    height: string;
  } | null;
  author?: string;
  section?: string;
  modified_time?: string;
  published_time?: string;
  card: "summary" | "summary_large_image" | "app" | "player";
};

const OpenGraph = ({ properties }: { properties: OGProperties }) => {
  const {
    locale,
    url,
    site_name,
    title,
    type,
    description,
    author,
    section,
    image,
    modified_time,
    published_time,
    card,
  } = properties;

  return (
    <>
      <meta property="og:locale" content={locale || "en_US"} />
      <meta property="og:title" content={title} />
      <meta property="og:type" content={type} />
      <meta property="og:description" content={description || ""} />
      <meta property="og:url" content={url} />
      <meta property="og:site_name" content={site_name} />
      {type === "article" && (
        <>
          <meta property="article:author" content={author} />
          <meta property="article:section" content={section} />
          <meta property="article:modified_time" content={modified_time} />
          <meta property="article:published_time" content={published_time} />
        </>
      )}
      {image && (
        <>
          <meta property="og:image" content={image.url} />
          <meta
            property="og:image:secure_url"
            content={image.url.replace("http://", "https://")}
          />
          <meta property="og:image:width" content={image.width} />
          <meta property="og:image:height" content={image.height} />
          <meta property="og:image:alt" content={image.alt} />
          <meta property="og:image:type" content={image.type} />
          <meta name="twitter:image" content={image.url} />
        </>
      )}
      <meta name="twitter:card" content={card} />
      <meta name="twitter:url" content={url} />
      <meta name="twitter:domain" content="codersteps.com" />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description || ""} />
      <meta name="twitter:site" content="@codersteps" />
      <meta name="twitter:creator" content="@abdessamadely" />
    </>
  );
};

export default OpenGraph;

Take a minute to read the OpenGraph component content, after that let me highlight the parts that may need a little explanation.

First, we have marked the locale as optional and provided it with a default of "en_US", then we added other simple og:tags the same way we did with the locale property.

These are simple open graph tags types: og:locale, og:title, og:type, og:description, og:url, og:site_name, og:image. because the image is optional, we check for it, before adding the og:image related meta tags.

With each property, we used the same process, first adding the property the the OGProperties type, then extracting it using object destructuring, and finally adding it to the meta tags JSX.

When the og:type is equal to article, we add extra open graph meta tags related to the article: article:author, article:section, article:modified_time, article:published_time.

Whenever we add a new og meta tag, we extend the OGProperties type to include the new property that was added.

And at the end, we added Twitter-related open graph meta tags, which use the HTML meta tag with a name attribute instead of the property attribute.

Some value you can keep static like your domain name, twitter site (your website twitter account), or your own twitter account (creator).

You can always read more about this property from the mentioned blog post above, and you can always use your new knowledge to extend this component, but these are the most used open graph meta tags.

Building the useOpenGraph hook with Next.js

We could have used the component without the need of creating a hook for that, but it will be a little missy as our OpenGraph component take one little big object, which can make it a little hard to be reused in multiple places while maintaining the ease of making changes later on.

So to simplify our life and make the component easy to use in multiple places, we will create a helper hook, that will play the role of building the OpenGraph properties prop 😁.

Let's create a new file for our hook at hooks/useOpenGraph.ts then declare, and export the hook function.

hooks/useOpenGraph.ts
TypeScript
import { useMemo } from "react";

type PageOgData = {};

const useOpenGraph = (data: PageOgData) => {
  const ogProperties = useMemo<{}>(() => {
    return {};
  }, []);

  return ogProperties;
};

export default useOpenGraph;

This is the basic structure of the useOpenGraph hook, we use the react useMemo hook to store the OpenGraph component properties.

The hook accepts a data prop of type PageOgData which is similar to the OGProperties type with some small differences like making some properties that can have default values optional.

The useOpenGraph takes some data, then use it to build and manage the OpenGraph component properties, and finally returns the properties object, to be passed to the OpenGraph component.

Add the absUrl helpers function

To create the absUrl function we will need the base url like http://localhost:3000, we can use it directly, but it will make out life pretty hard when we move to the production environment.

So we will add this value to the .env.local file, create a new file .env.local, then add to it the APP_URL env variable.

.env.local
Bash
APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=${APP_URL}

When we prefix an environment variable with NEXT_PUBLIC_ next.js will expose it to be used publicity, in case we used our absUrl helper inside a component that runs on the client, then this will be helpful.

You can create a config file, to export an object with your configurations, like we did in the How to add Google Analytics to your Next.js website article.

But it's not required for this article, to keep it simple we will use the process.env directly inside our helper.

core/helpers.ts
TypeScript
export const absUrl = (path: string): string => {
  path = path.trim();
  if (path.startsWith("http")) {
    return path;
  }
  if (path.indexOf("/") === 0) {
    path = path.substring(1);
  }

  const appUrl =
    process.env.APP_URL ||
    process.env.NEXT_PUBLIC_APP_URL ||
    "http://localhost:3000";

  return `${appUrl}/${path}`;
};

The helper function is pretty straightforward, we accept a path, trim it to remove any extra spaces, then check if it's already an absolute path, by checking if it starts with http.

If the path is a relative path, we check to make sure it doesn't have a preceding slash, so we won't have double slashes like http://localhost:3000//relative-path

And finally we store out APP_URL into a constant and return a combination between the appUrl and the path.

Implementation of the useOpenGraph functionality

After we understood the purpose of our hook and added the needed helpers, its time to implement the full functionality.

hooks/useOpenGraph.ts
TypeScript
import { useMemo } from "react";
import { absUrl } from "../core/helpers";
import { OGProperties } from "../components/common/OpenGraph";

type OGImage = {
  alt: string;
  type: string;
  url: string;
  width?: string;
  height?: string;
} | null;

type PageOgData = Omit<OGProperties, "image" | "card" | "site_name"> & {
  card?: OGProperties["card"];
  image: OGImage;
};

export const useOpenGraph = (data: PageOgData) => {
  const ogProperties = useMemo<OGProperties>(() => {
    return {
      url: data.url,
      title: data.title,
      type: data.type,
      author: data.author,
      site_name: "OpenGraph Article",
      description: data.description,
      image: data.image
        ? {
            type: data.image.type,
            url: absUrl(data.image.url),
            alt: data.image.alt || "",
            height: data.image.height || "720",
            width: data.image.width || "420",
          }
        : null,
      card: data.card || data.image ? "summary_large_image" : "summary",
      section: data.section,
      modified_time: data.modified_time,
      published_time: data.published_time,
    };
  }, [data]);

  return ogProperties;
};

export default useOpenGraph;

It's a simple react hook, by looking at it you can understand its purpose, and you may not see its added value now, but I think t's a good practice, this will allow you to do changes later without having to do the changes on each page it uses it.

For example, we made our site_name a static value, as its rarely that we do rename our website, Also we have made the card prop optional with a default value related to if we have an image or not.

We also use the absUrl to make sure that our image url is an absolute url, you can customize it to fit your use case, maybe you have to make some changes like adding prefix to your path like: {url: absUrl(/uploads/${upload.path})}.

Add OpenGraph to the home page

At this point, we should have a working open-graph setup with its own component and a helper hook. It's time to put it to use on the home page.

Open your pages/index.tsx file and do the following changes, then we'll explain the changes, they're not that complex.

pages/index.tsx
TypeScript
import type { NextPage } from "next";
import Head from "next/head";
import OpenGraph from "../components/common/OpenGraph";
import { useOpenGraph } from "../hooks/useOpenGraph";
import { absUrl } from "../core/helpers";

const Home: NextPage = () => {
  const ogProperties = useOpenGraph({
    url: absUrl("/"),
    title: "OpenGraph Article", // Add you homepage title
    image: {
      // some default image preview for your website
      type: "image/jpeg",
      url: "/assets/images/ogImage.jpg",
      alt: "Image Alt",
    },
    description: "Your website description",
    type: "website",
  });

  return (
    <div>
      <Head>
        <title>OpenGraph with Next.js</title>
        <meta
          name="description"
          content="Article about OpenGraph with Next.js"
        />

        <OpenGraph properties={ogProperties} />
      </Head>

      <main>
        <h1>OpenGraph with Next.js</h1>
        <div>An empty page is a good page</div>
      </main>
    </div>
  );
};

export default Home;

First, we used the useOpenGraph helper hook to build the OpenGraph properties, which we'll be passed to the OG component.

After that inside the Head component, we add the OpenGraph component providing it with the properties we created using the useOpenGraph helper hook.

Finally, it's time to test, visit the home page on your browser, then View Page Source and search for one of the OG meta tags that we have added like og:title.

Add OpenGraph to the show post page

In the same way, we used the OpenGraph component on the home page we will use it on the show post page, with a little different input like the og:type will be an article instead of website and we need to pass the article related data.

Open your pages/posts/[id].tsx file and do the following changes, then we'll explain the changes.

pages/posts/[id].tsx
TypeScript
import Head from "next/head";
import Image from "next/image";
import { GetServerSideProps, NextPage } from "next";
import OpenGraph from "../../components/common/OpenGraph";
import useOpenGraph from "../../hooks/useOpenGraph";
import { absUrl } from "../../core/helpers";

type Post = {
  id: number;
  title: string;
  thumbnail: string;
  description: string;
  content: string;
};

interface ServerSideProps {
  post: Post;
}

const ShowPost: NextPage<ServerSideProps> = ({ post }) => {
  const ogProperties = useOpenGraph({
    url: absUrl(`/posts/${post.id}`),
    title: post.title,
    image: {
      // The post thumbnail
      type: "image/jpeg", // replace it with a dynamic thumbnail mimetype
      url: post.thumbnail,
      alt: post.title,
    },
    description: post.description,
    type: "article",
    author: "Article Author",
    section: "Article Category",
    modified_time: "Post Updated Time",
    published_time: "Post Published Time",
  });

  return (
    <>
      <Head>
        <title>{post.title}</title>

        <OpenGraph properties={ogProperties} />
      </Head>

      <main>
        <article>
          <h1>{post.title}</h1>

          <Image
            width={720}
            height={420}
            src={post.thumbnail}
            alt={post.title}
          />

          <div dangerouslySetInnerHTML={{ __html: post.content }}></div>
        </article>
      </main>
    </>
  );
};

export const getServerSideProps: GetServerSideProps<ServerSideProps> = async ({
  params,
}) => {
  if (!params || typeof params.id !== "string" || isNaN(+params.id)) {
    return { notFound: true };
  }

  const posts: Post[] = require("../../data/posts.json");

  const postId = +params.id;
  const post = posts.find((p) => p.id === postId);
  if (!post) {
    return { notFound: true };
  }

  return {
    props: {
      post,
    },
  };
};

export default ShowPost;

The same thing here, we build the ogProperties and then added the OpenGraph component to the Head.

The only difference is that we used dynamic data, for example, the posts data structure that we use doesn't have many properties to use but, in a real-life situation, you may want to make everything dynamic like the image mime-type, update and publish dates, etc...

You can now navigate to http://localhost:3000/posts/3 then View Page Source and search for something like article:author.

Conclusion

In this article, we have added OpenGraph meta tags to a fresh Next.js website, we used two pages as examples: home page and show post page, in order to demonstrate how to add the OpenGraph tags to multiple types of pages.

Source code is available at: https://github.com/codersteps/nextjs_open_graph.

That's it for this one, I hope it was helpful, see you in the next one 😉.