This content originally appeared on DEV Community and was authored by Kacey Cleveland
Introduction
The Open Graph Protocol (https://ogp.me/) allows for parsing of specific metadata that many social networks utilize to create dynamic sharable content. An example of this could be when you share a post on Facebook with a link but when you actually share it, the link is joined with a description, an author, an even a cover photo/picture. We can take it a step further and generate the photo/picture and also populate the other metadata fields. This article will focus on creating dynamic images based on your dynamic pages. I utilize this method deploying to Vercel for this blog on my website (https://kleveland.dev).
Tech used
- NextJS
- Serverless functions (via Vercel/AWS)
Example
https://www.kleveland.dev/posts/create-notion-blog
When I try and share one of my blog posts on Linkedin, you can see it gets populated with a preview image and text. We will go over how that image is generated and how we can customize it.
How It Works
As a starting point, I am going to assume you have some dynamic content/pages in a NextJS application. In my case, I utilize the following files for this blog:
Pages:
- /pages/posts/[slug].tsx
- /pages/posts/open-graph/[slug].tsx
- /pages/api/open-graph-image.ts
Utils:
- /utils/use-open-graph-image.ts
- /utils/utils.ts
The code is actually borrowed heavily from here with a set of adjustments to make it more customizable:
https://playwright.tech/blog/generate-opengraph-images-using-playwright
api/open-graph-image
// path: /pages/api/open-graph-image.ts
import type { NextApiRequest, NextApiResponse } from "next";
import chromium from 'chrome-aws-lambda';
import { chromium as playwrightChromium } from 'playwright-core';
// getAbsoluteURL is in a snippet further down
import { getAbsoluteURL } from 'utils/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Start the browser with the AWS Lambda wrapper (chrome-aws-lambda)
const browser = await playwrightChromium.launch({
args: chromium.args,
executablePath: await chromium.executablePath,
headless: chromium.headless,
})
// Create a page with the Open Graph image size best practise
// 1200x630 is a good size for most social media sites
const page = await browser.newPage({
viewport: {
width: 1200,
height: 630
}
});
// Generate the full URL out of the given path (GET parameter)
const relativeUrl = (req.query["path"] as string) || "";
const url = getAbsoluteURL(relativeUrl)
await page.goto(url, {
timeout: 15 * 1000,
// waitUntil option will make sure everything is loaded on the page
waitUntil: "networkidle"
})
const data = await page.screenshot({
type: "png"
})
await browser.close()
// Set the s-maxage property which caches the images then on the Vercel edge
res.setHeader("Cache-Control", "s-maxage=31536000, stale-while-revalidate")
res.setHeader('Content-Type', 'image/png')
// write the image to the response with the specified Content-Type
res.end(data)
}
getAbsoluteURL
// Gets the URL for the current environment
export const getAbsoluteURL = (path: string) => {
const baseURL = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"
return baseURL + path
}
use-open-graph-image
import { useRouter } from "next/router";
import { getAbsoluteURL } from "./utils";
export default function useOpenGraphImage() {
const router = useRouter();
const searchParams = new URLSearchParams();
// The [slug] from /posts/[slug] and /posts/open-graph/[slug]
// should be identical.
searchParams.set(
"path",
router.asPath.replace("/posts/", "/posts/open-graph/")
);
// Open Graph & Twitter images need a full URL including domain
const fullImageURL = getAbsoluteURL(`/api/open-graph-image?${searchParams}`);
return { imageURL: fullImageURL };
}
pages/posts/[slug]
Both of these files should generate the same slugs; the open-graph route slug will correspond to the image for the corresponding article from /pages/posts/[slug].tsx. For example, this article on my website has this route:
https://www.kleveland.dev/posts/create-notion-blog
and if I want the open graph image for that route, I can go to:
https://www.kleveland.dev/posts/open-graph/create-notion-blog
The part that matters is the usage of the custom hook in /pages/posts/[slug].tsx that will get us the imageURL to pass to the meta tags:
import Head from "next/head";
const postComponent = (props) => {
const { imageURL } = useOpenGraphImage(); // <- This custom hook here!
return <>
<Head>
<title>Kacey Cleveland - {title}</title>
<meta name="description" content={props.description} />
<meta property="og:title" content={props.title} />
<meta property="og:type" content="article" />
<meta property="og:image" content={imageURL} />
</Head>
<div>
// Content here
</div>
</>;
}
/utils/use-open-graph-image.ts
import { useRouter } from "next/router";
import { getAbsoluteURL } from "./utils";
export default function useOpenGraphImage() {
const router = useRouter();
const searchParams = new URLSearchParams();
searchParams.set(
"path",
router.asPath.replace("/posts/", "/posts/open-graph/") // This will take the current URL of the post and give us the open-graph one. Modify as needed for how you have your routing setup
);
const fullImageURL = getAbsoluteURL(`/api/open-graph-image?${searchParams}`); // This will then pass along the route for the open-graph image to our api request which will run the serverless function which runs headless chrome and goes to the /posts-open-graph/[slug].tsx route and takes a screenshot to serve as the 'fullImageURL' return.
return { imageURL: fullImageURL };
}
Fin
TLDR the order of operations are the following:
- A user shares a link to your article/dynamic content
- The site that the article is shared on finds reads the meta tags and finds there is an open graph image tag
- The image URL is a GET request to a serverless function that will take a screenshot of the passed route (/posts/open-graph/[slug].tsx) and return the image to be served on the social media site the link was shared on.
Additional Resources
This content originally appeared on DEV Community and was authored by Kacey Cleveland

Kacey Cleveland | Sciencx (2021-06-25T02:36:11+00:00) Generating Sharable Content Images with Open Graph and NextJS. Retrieved from https://www.scien.cx/2021/06/25/generating-sharable-content-images-with-open-graph-and-nextjs/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.