📷
Dynamically Generating OGP Images during SSG Build with Next.js App Router.
Posted on
Updated on
Introduction
This article introduces the implementation method of implementing OGP generation on this site, and how to dynamically generate OGP during SSG build with Next.js App Router.
In this article, when we mention Next.js, we are referring to Next.js App Router.
Target Audience
- Those using Static Site Generation (SSG) with Next.js App Router
- Those who want to implement OGP generation
- Those who want to generate OGP during build time
Environment
- Next.js 15.3.2
- @vercel/og (included with Next.js)
OGP Basics
What is OGP?
OGP stands for Open Graph Protocol, a protocol used to specify information such as images, titles, and descriptions that appear when content is shared on social networks.
The information displayed in link cards like the one below is an example of OGP.
Main OGP Meta Tags
The main meta tags used in OGP are as follows:
<meta property="og:title" content="Page Title" />
<meta property="og:description" content="Page Description" />
<meta property="og:type" content="website" />
<meta property="og:url" content="Page URL" />
<meta property="og:image" content="Image URL" />
<meta property="og:site_name" content="Site Name" />
Implementing OGP in Next.js
1. Static OGP Configuration
In Next.js, you can set up static OGP by placing a file named opengraph-image.(jpg|jpeg|png|gif)
in any route. It will be automatically included in the metadata.
<meta property="og:image" content="<generated>" />
<meta property="og:image:type" content="<generated>" />
<meta property="og:image:width" content="<generated>" />
<meta property="og:image:height" content="<generated>" />
<meta name="twitter:image" content="<generated>" />
<meta name="twitter:image:type" content="<generated>" />
<meta name="twitter:image:width" content="<generated>" />
<meta name="twitter:image:height" content="<generated>" />
If the file is not placed in the corresponding route, the file from the parent route will be used.
2. Dynamic OGP Generation
With App Router, since @vercel/og
is included, you can easily generate OGP using next/og
.
2.1 Basic Implementation
By generating in a route like /posts/[slug]/og.png/route.tsx
, you can retrieve the OGP at https://example.com/posts/slug/og.png
.
In this configuration, we can generate OGP during build time without using edge functions like /api/og
.
import path from 'path';
import { readFile } from 'fs/promises';
import { ImageResponse } from 'next/og';
// Specify static generation during build
export const dynamic = 'force-static';
// Return 404 if not generated
export const dynamicParams = false;
interface Post {
slug: string;
title: string;
}
// Test data
const testPosts: Post[] = [
{
slug: 'test-post-1',
title: 'Test Post 1',
},
{
slug: 'test-post-2',
title: 'Test Post 2',
},
];
// Pre-generate dynamic parameters statically
export async function generateStaticParams() {
return testPosts.map((post) => ({
slug: post.slug,
}));
}
export async function GET(_: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = testPosts.find((post) => post.slug === slug);
if (!post) {
return new Response('Not Found', { status: 404 });
}
try {
// When there are no image URLs or data available during build time, you can load image files from your project like this
const bgData = await readFile(path.join(process.cwd(), 'public', 'opengraph-image-bg.png'));
const bgSrc = Uint8Array.from(bgData).buffer;
const fontData = await loadFont(post.title);
return new ImageResponse(
(
<div
style={{
fontFamily: 'Noto Sans JP',
fontWeight: 700,
fontSize: 48,
color: 'white',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
}}
>
<img src={bgSrc} tw="w-full h-full object-cover" />
<div tw="flex flex-col items-center justify-center w-full h-full">
<div tw="text-4xl font-bold text-white">{post.title}</div>
</div>
</div>
),
{
fonts: [
{
name: 'Noto Sans JP',
data: fontData,
style: 'normal',
weight: 700,
},
],
width: 1200,
height: 630,
}
);
} catch (error) {
console.error('OG image generation error:', error);
return new Response('Failed to create OG image', { status: 500 });
}
}
2.2 Font Loading
When generating OGP images that include Japanese text, you need to explicitly specify the font. Here's an example implementation that loads a subset font from Google Fonts.
export async function loadFont(subset: string) {
try {
// Fetch subset font from Google Fonts
const fontResponse = await fetch(
`https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@700&text=${encodeURIComponent(subset)}`
);
if (!fontResponse.ok) {
throw new Error('Failed to fetch font CSS');
}
const fontCss = await fontResponse.text();
const fontUrl = fontCss.match(/url\((.*?)\)/)?.[1];
if (!fontUrl) {
throw new Error('Failed to extract font URL');
}
const fontDataResponse = await fetch(fontUrl);
if (!fontDataResponse.ok) {
throw new Error('Failed to fetch font data');
}
const fontData = await fontDataResponse.arrayBuffer();
return fontData;
} catch (error) {
console.error(error);
return new Response('Failed to load font', { status: 500 });
}
}
2.3 Applying Generated OGP
To apply the generated OGP image to your site, set the metadata as follows.
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata | undefined> {
const { slug } = await params;
return {
openGraph: {
images: [
{
url: `https://example.com/posts/${slug}/og.png`,
width: 1200,
height: 630,
alt: `Post ${slug}`,
type: 'image/png',
},
],
},
};
}
3. Implementation Notes
-
Font Loading
- When including Japanese text, you must explicitly specify the font
- Even with only alphabets, I encountered errors in my environment
- You can use Google Fonts' subsetting feature to load only the necessary characters
-
Image Size
- The recommended size for OGP images is
1200x630
pixels - Since many social networks crop to 1:1, it's recommended to place important content in the center
630x630
area
- The recommended size for OGP images is
-
Build-time Generation
- Specify
dynamic = 'force-static'
to generate statically during build - You need to specify the parameters to generate in
generateStaticParams
- Specify
-
Error Handling
- Proper error handling is necessary for font loading and image generation
-
Style Specification
- The
display
property ofdiv
must be explicitly set to eitherflex | block | none
- The
-
Metadata Configuration
- Metadata can be set using
generateMetadata
- Missing or incorrect metadata settings can negatively impact SEO
- Metadata can be set using
Summary
With Next.js App Router, you can generate dynamic OGP images during SSG build time by making some adjustments to the routing.
By paying attention to proper Japanese font loading and metadata configuration, and setting appropriate image sizes, you can generate better OGP images.