In this tutorial, you will learn everything you need to know regarding Open Graph basics and how to integrate it on your Next.js website.
I will start by creating a new Next.js project using its default options with TailwindCSS and TypeScript, and that's with npx create-next-app@latest
.
Once the project is ready, open it in your favourite code editor, then start the development server with npm run dev
.
About OpenGraph
Before we touch any code, let's first learn a little bit about Open Graph and its properties, which we'll be adding to our Next.js website.
What is OpenGraph?
Open Graph is a set of meta tags that help social media platforms better understand our web pages.
This gives us control over how platforms like Facebook, LinkedIn, and X display previews of our pages when they’re shared in the feed.
When a web page is shared on social media, if Open Graph metadata isn’t present, platforms will fall back to whatever is available—such as the first image or text they find.
What are OpenGraph meta tags?
In this tutorial, we will add to a new Next.js website, the basic Open Graph meta tags for it to be considered valid:
<meta property="og:title" content="page title" />
<meta property="og:description" content="short description" />
<meta property="og:type" content="page type" />
<meta property="og:url" content="page canonical url" />
<meta property="og:image" content="image-url" />
<meta property="og:image:alt" content="image-alt" />
<meta property="og:image:type" content="image-MIME-type" />
<meta property="og:image:width" content="image-width" />
<meta property="og:image:height" content="image-height" />
To learn more about available Open Graph meta tags and their possible values, you can always visit the official ogp.me website.
For example, the og:type
meta tag has a set of predefined values, but in our case, we’ll use the website
and article
types.
Open Graph Next.js project
Now that we have a basic understanding of Open Graph, let’s create a new Next.js project and set up some basic pages.
Please skip this section if you already have a project, otherwise go ahead and create a fresh Next.js project then go to the next section.
To make it easy, we'll use an example of a blog, where we have different type of pages like:
- Landing page
- Listing page
- Detail page
Let's add Open Graph to each one of them.
Add Open Graph to a landing page
Mostly, for landing pages, we have a static image that represents the brand, so let's consider the image is stored at: /public/og-image-home.jpg
.
Open graph images should respect the recommend aspect ratio 1.91:1, something like 1200x630 or 600x315, to avoid being cropped.
For example, for my website's homepage I use this 600x315 image:

Let's update our homepage then explain how it works:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Landing page for Open Graph tutorial',
description: 'Learn about how to add OpenGraph to your Next.js website',
openGraph: {
title: 'Landing page for Open Graph tutorial',
description: 'Learn about how to add OpenGraph to your Next.js website',
type: 'website',
url: process.env.APP_URL || 'http://localhost/',
images: {
url: `${process.env.APP_URL || 'http://localhost/'}/og-image-home.jpg`,
alt: 'Open Graph image for Abdessamad Ely Homepage',
type: 'image/jpeg',
width: 600,
height: 315,
},
},
}
export default function Home() {
return (
<main>
<h1>Landing page for Open Graph tutorial</h1>
<p>Learn about how to add OpenGraph to your Next.js website</p>
</main>
)
}
Using Next.js static metadata, we export the metadata
object with an openGraph
attribute, which defines our page’s Open Graph metadata.
You can also use Next.js Metadata files convention, by putting opengraph-image.jpg
image file next to your page.tsx
.
Learn more about metadata conventions for Open Graph images.
We also used process.env.APP_URL
to access the configured APP_URL
from our .env
file, so make sure it’s defined, as OG tags works with absolute URLs.
APP_URL=https://abdessamadely.com/
Now, if you visit your website’s landing page, you should be able to inspect and check the added meta tags.

We can also add Open Graph to any Next.js static page following the same approach as the homepage.
Add Open Graph to a listing page
While the listing page is not static in terms of the content, it is considered static in terms of metadata, as its canonical URL, title, and description don’t change.
Please apply the same approach as in the landing page section above.
Add Open Graph to a detail page
For detail page, we will use the following data/posts.json
file as our dynamic content:
[
{
"id": 1,
"title": "Post 1",
"slug": "post-1",
"thumbnail": "/images/og-posts-image.jpg",
"description": "Description of post 1",
"content": "Content of post 1"
},
{
"id": 2,
"title": "Post 2",
"slug": "post-2",
"thumbnail": "/images/og-posts-image.jpg",
"description": "Description of post 2",
"content": "Content of post 2"
},
{
"id": 3,
"title": "Post 3",
"slug": "post-3",
"description": "Description of post 3",
"content": "Content of post 3"
}
]
We have a list of 3 posts, 2 posts have a thumbnail image which can be used for the og:image
, but the last post doesn't have an image.
This way, we will learn about using dynamically generated metadata, as well as generating Open Graph images on the fly with Next.js ImageResponse
.
Set up a post detail page
Let's first have a working detail page based on our posts.json
file:
import type { Metadata } from 'next'
import posts from '@/data/posts.json'
import { notFound } from 'next/navigation'
type Props = {
params: Promise<{ slug: string }>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const slug = (await params).slug
const post = posts.find((p) => p.slug === slug)
if (!post) {
return {}
}
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: 'article',
url: new URL('/posts/' + post.slug, process.env.APP_URL).toString(),
images: {
url: post.thumbnail
? new URL(post.thumbnail, process.env.APP_URL).toString()
: new URL(
`/posts/${post.slug}/opengraph-image`,
process.env.APP_URL
).toString(),
alt: `Open Graph image for ${post.title}`,
type: 'image/jpeg',
width: 600,
height: 315,
},
},
}
}
export default async function Post({ params }: Props) {
const slug = (await params).slug
const post = posts.find((p) => p.slug === slug)
if (!post) {
notFound()
}
return (
<main>
<h1>{post.title}</h1>
{post.thumbnail && <div>Post with thumbnail: {post.thumbnail}</div>}
<p>{post.description}</p>
<article dangerouslySetInnerHTML={{ __html: post.content }}></article>
</main>
)
}
We've added a simple post detail page, which lookup a post by it's slug
, and show the post details.
We also added dynamic metadata by exporting the Next.js generateMetadata function, then we use the post information to set the page’s metadata.
When the post have a thumbnail we manually set the og:image
properties, if not we just set it to undefined
so we can dynamically generate it.
Creating a OpenGraph Image page
Let's start by creating a new file next to our post detail page at app/posts/[slug]/opengraph-image.tsx
:
import posts from '@/data/posts.json'
import { ImageResponse } from 'next/og'
import { notFound } from 'next/navigation'
export const contentType = 'image/png'
export default async function Image({ params }: { params: { slug: string } }) {
const slug = (await params).slug
const post = posts.find((p) => p.slug === slug)
if (!post || post.thumbnail) {
notFound()
}
return new ImageResponse(
(
<div
style={{
gap: 12,
width: '100%',
height: '100%',
padding: 34,
display: 'flex',
background: 'white',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<div
style={{
fontSize: 48,
gap: 12,
display: 'flex',
alignItems: 'center',
fontWeight: 'bold',
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
width={48}
height={48}
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5"
/>
</svg>
<span>{post.title}</span>
</div>
<div
style={{
fontSize: 18,
}}
>
{post.description}
</div>
</div>
),
{
width: 600,
height: 315,
}
)
}
The opengraph-image.tsx
filename, is special for generating og images, you can also use twitter-image.tsx
if you want to have different image for Twitter/X.
With the help of Next.js ImageResponse
its simple to create an OG Image template from combining HTML, SVG and CSS.
We also export the contentType
so that browsers correctly render it as an image.
Now, if you go to the /posts/post-3/opengraph-image
page, you should see an image with an icon, title, and description.
Also if you inspect one of the post detail pages, you will see that Open Graph meta tags were added correctly.
Conclusion
This tutorial, covered how you can add OpenGraph meta tags to a Next.js website, we focused mainly on two type of pages.
A page with static metadata, and a page with dynamically generated metadata, with a practical example of a simple blog.