← 전체 글로 돌아가기

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 공유 미리보기는 캐시가 길어서 바로 반영이 안 될 수 있다. 테스트할 때는 아래 도구를 쓴다.

twitter:image와 og:image의 차이

opengraph-image.tsxog:image만 설정한다. 트위터는 별도로 twitter:image를 보기도 하는데, Next.js는 og:imagetwitter: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' },
  };
}