Docker
Next.js generateMetadata에서 og:image를 동적으로 생성하는 방법
정적 og:image 하나로 모든 글에 같은 이미지를 쓰면 SNS 공유 미리보기가 단조롭다. 글 제목을 이미지에 넣는 방법을 정리했다.
블로그 글을 카카오톡이나 트위터에 공유했을 때 미리보기 이미지가 글 제목을 담고 있으면 훨씬 눈에 띈다. Next.js는 파일 기반으로 동적 OG 이미지를 생성하는 기능을 내장하고 있다.
파일 이름이 곧 설정
app/posts/[slug]/opengraph-image.tsx 파일을 만들면 Next.js가 자동으로 /posts/{slug}/opengraph-image 경로를 만들고, og:image 메타태그에 연결한다. generateMetadata에서 따로 설정할 필요가 없다.
// app/posts/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { getPost } from '@/lib/posts';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return new ImageResponse(
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
padding: 60,
width: '100%',
height: '100%',
background: '#0a0a0a',
color: '#f5f5f5',
}}
>
<div style={{ fontSize: 18, color: '#888', marginBottom: 16 }}>turin's blog</div>
<div style={{ fontSize: 48, fontWeight: 700, lineHeight: 1.2 }}>
{post?.title ?? '글을 찾을 수 없습니다'}
</div>
</div>,
{ ...size }
);
}
커스텀 폰트 적용
기본으로는 시스템 폰트가 쓰인다. 한국어 폰트를 쓰려면 폰트 파일을 직접 로드해야 한다.
export default async function Image({ params }: { params: { slug: string } }) {
const fontData = await fetch(
new URL('/fonts/Pretendard-Bold.woff', import.meta.url)
).then((res) => res.arrayBuffer());
const post = await getPost(params.slug);
return new ImageResponse(
<div style={{ fontFamily: 'Pretendard', /* ... */ }}>
{post?.title}
</div>,
{
...size,
fonts: [{ name: 'Pretendard', data: fontData, style: 'normal' }],
}
);
}
폰트 파일은 public/fonts/에 넣어두고 절대 경로로 불러온다. import.meta.url을 쓰는 방식은 Edge Runtime에서도 동작한다.
실제로 잘 나오는지 확인
개발 중에는 브라우저에서 직접 /posts/{slug}/opengraph-image 경로를 열어보면 된다. 이미지가 렌더링되지 않으면 콘솔에 에러가 찍힌다.
SNS 공유 미리보기는 캐시가 길어서 바로 반영이 안 될 수 있다. 테스트할 때는 아래 도구를 쓴다.
- 트위터: cards-dev.twitter.com/validator (현재는 X Developer Portal)
- 카카오: developers.kakao.com/tool/debugger/sharing
- Facebook: developers.facebook.com/tools/debug
twitter:image와 og:image의 차이
opengraph-image.tsx는 og:image만 설정한다. 트위터는 별도로 twitter:image를 보기도 하는데, Next.js는 og:image를 twitter:image로도 폴백해서 쓰기 때문에 대부분은 따로 설정할 필요가 없다. generateMetadata에서 twitter: { card: 'summary_large_image' }만 명시해두면 충분하다.
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post?.title,
description: post?.excerpt,
twitter: { card: 'summary_large_image' },
};
}