기술 포스트

OG 이미지를 코드로 만든다


Article

블로그 포스트가 늘어나면서 OG 이미지가 골칫거리가 됐다. 링크를 공유했을 때 미리보기에 뜨는 그 카드 이미지 말이다. 처음엔 디자인 툴로 하나하나 만들었다. 포스트 하나 쓸 때마다 툴 켜고, 제목 바꾸고, 내보내고, 업로드하고. 열 개쯤 만들고 나니 이건 사람이 할 일이 아니라는 생각이 들었다.

그래서 코드로 옮겼다. Next.js의 next/og, 정확히는 ImageResponse를 쓰면 JSX로 이미지를 그릴 수 있다. 폰트, 레이아웃, 색을 코드로 정의하면 런타임에 PNG가 나온다.

opengraph-image로 페이지마다 자동 생성

App Router에서는 라우트 폴더 안에 opengraph-image.tsx를 두면 그 페이지의 OG 이미지가 된다. 포스트 상세라면 p/[slug]/opengraph-image.tsx에 이렇게 쓴다.

import { ImageResponse } from "next/og";
import { getPost } from "@/lib/github";

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={{
          width: "100%",
          height: "100%",
          display: "flex",
          flexDirection: "column",
          justifyContent: "center",
          padding: "80px",
          background: "#0a0a0a",
          color: "#ffffff",
        }}
      >
        <div style={{ fontSize: 28, color: "#f87171", letterSpacing: 4 }}>
          DCODELAB
        </div>
        <div style={{ fontSize: 64, fontWeight: 700, marginTop: 24 }}>
          {post.title}
        </div>
        <div style={{ fontSize: 28, color: "#b5b5b5", marginTop: 20 }}>
          {post.description}
        </div>
      </div>
    ),
    { ...size },
  );
}

여기서 쓰는 건 진짜 CSS가 아니라 Satori가 지원하는 flexbox 서브셋이다. display: flex를 거의 필수로 붙여야 하고, grid나 복잡한 속성은 안 먹는다. 처음엔 이걸 몰라서 레이아웃이 자꾸 깨졌다. 텍스트 여러 개를 세로로 쌓을 때 부모에 flexDirection: column을 빼먹으면 다 겹쳐 나온다.

metadata에 images를 명시해야 크롤러가 잡는다

여기서 실수하기 쉬운 부분. opengraph-image.tsx만 있으면 될 것 같지만, 실제로는 generateMetadata에서 openGraph.imagestwitter.images를 명시적으로 선언해 두는 편이 안전하다. 파일 규약만 믿고 있다가 일부 크롤러가 이미지를 못 잡는 경우를 겪었다.

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  const ogUrl = `${SITE_URL}/p/${params.slug}/opengraph-image`;

  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [{ url: ogUrl, width: 1200, height: 630 }],
    },
    twitter: {
      card: "summary_large_image",
      images: [ogUrl],
    },
  };
}

opengraph-image.tsx가 자동으로 메타 태그를 넣어주긴 하지만, 명시적으로 URL을 박아두면 크롤러 호환성이 확실히 올라간다. 카카오나 슬랙, 트위터가 각자 조금씩 다르게 동작하기 때문에 이 이중 안전장치가 마음이 편하다.

결과

이제 포스트를 쓰면 OG 이미지가 자동으로 나온다. 제목이 길든 짧든 코드가 알아서 배치한다. 디자인 톤을 바꾸고 싶으면 컴포넌트 하나만 고치면 전체 포스트에 반영된다. 예전에 툴로 하던 반복 작업이 통째로 사라졌다.

디자인 전공으로 시작해서 이런 걸 코드로 옮길 때마다 좀 묘한 기분이 든다. 손으로 하던 걸 시스템으로 만드는 것. 결국 그게 퍼블리셔에서 프론트로 넘어온다는 것의 의미 같기도 하다.

OG 이미지를 코드로 만든다 — 디코드랩(DCODELAB)