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.
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.
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.
src/app/posts/[slug]/og.png/route.tsx
import path from 'path';import { readFile } from 'fs/promises';import { ImageResponse } from 'next/og';// Specify static generation during buildexport const dynamic = 'force-static';// Return 404 if not generatedexport const dynamicParams = false;interface Post { slug: string; title: string;}// Test dataconst testPosts: Post[] = [ { slug: 'test-post-1', title: 'Test Post 1', }, { slug: 'test-post-2', title: 'Test Post 2', },];// Pre-generate dynamic parameters staticallyexport 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.
src/app/posts/[slug]/og.png/route.tsx
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.
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
Build-time Generation
Specify dynamic = 'force-static' to generate statically during build
You need to specify the parameters to generate in generateStaticParams
Error Handling
Proper error handling is necessary for font loading and image generation
Style Specification
The display property of div must be explicitly set to either flex | block | none
Metadata Configuration
Metadata can be set using generateMetadata
Missing or incorrect metadata settings can negatively impact SEO
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.