@vercel/og 라이브러리로 동적 Open Graph 이미지 생성하기

개요

블로그 게시글을 다국어로 제공하고 나서, 언어를 바꿀 때 텍스트는 바뀌지만 썸네일은 바뀌지 않아서 내용은 영어인데 이미지는 한글인 경우가 나타나 어색해 보이는 일이 있었다. 이를 이유로 이미지를 동적으로 생성하는 것이 필요하다 싶어 조사하던 중 vercel에서 제공하는 라이브러리가 있어 사용해보았다.

Next.js에서 동적 OG 이미지 설정하기

Next.js는 Vercel의 @vercel/og 라이브러리를 통해 동적 OG 이미지 생성을 지원한다. 이 기능은 Vercel Functions 을 통해 계산되고 실행된다.

설치 방법

  • App Router 프로젝트: 추가 패키지 설치 필요 없음 (내장되어 있음)
    • import { ImageResponse } from "next/og"
  • Pages Router 프로젝트: npm i @vercel/og 명령어로 설치
    • import { ImageResponse } from "@vercel/og

기본 사용방법

스타일링

평소에 컴포넌트를 만드는 것처럼 만들면 된다. JSX 문법을 사용해서 만들기 때문에 api 이지만, 파일 확장자를 jsx 혹은 tsx 로 생성해야 한다.

아래와 같이 style을 직접 삽입해서 생성할 수 있고,

return new ImageResponse(
  <div
    style={{
      height: '100%',
      width: '100%',
      display: 'flex',
      flexDirection: 'column',
      alignItems: 'center',
      justifyContent: 'center',
      backgroundColor: '#fff',
      fontSize: 32,
      fontWeight: 600,
    }}
  >
    <div style={{ marginTop: 40 }}>Hello, World</div>
  </div>
)

tw 라는 props를 사용하여 tailwindCSS 문법을 적용할 수도 있다.

return new ImageResponse(
  <div
    tw="h-full w-full flex flex-col items-center justify-center bg-white text-xl font-bold"
  >
    <div tw="mt-10">Hello, World</div>
  </div>
)

Pages Router 프로젝트

/pages/api/og.tsx 경로에 파일을 만들고 다음과 같이 작성한다.

import { NextRequest } from "next/server";
import { ImageResponse } from "@vercel/og";

export const config = {
  runtime: "edge",
};

export default async function handler(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const title = searchParams.get("title");
  
  return new ImageResponse(
    (
      <div tw="flex flex-col w-full h-full items-center justify-center bg-white">
        <div tw="bg-gray-50 flex w-full">
          <div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
            <h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
              {title}
            </h2>
            <div tw="mt-8 flex md:mt-0">
              <div tw="flex rounded-md shadow">
                <a tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">
                  시작하기
                </a>
              </div>
              <div tw="ml-3 flex rounded-md shadow">
                <a tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">
                  더 알아보기
                </a>
              </div>
            </div>
          </div>
        </div>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}

Pages Routrer는 Node.js runtime을 지원하지 않기 때문에 Vercel Function이 Edge runtime에서 실행 되도록 아래 설정이 추가로 적용되어야 한다.

export const config = {
  runtime: "edge",
};

App Router 프로젝트

/app/api/og/route.tsx 파일을 만들고 다음과 같이 작성한다. Pages Router와 경로나 작성 방식에 차이가 있을 뿐, return 되는 값은 같다.

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const title = searchParams.get("title");

  // 이하 생략... 
}

코드에서 보듯이 searchParams 를 통해 값을 동적으로 받아와 보여줄 수 있다.

const title = searchParams.get("title");

이렇게 해서 다음 API를 호출 했을 때 브라우저에 나오는 결과는 다음과 같다. /api/og?title=WOW

Image.png

사용자 정의 폰트 추가하기

더 멋진 OG 이미지를 위해 사용자 정의 폰트를 추가해보자. 폰트 파일이 /public/fonts/ 아래에 있다고 했을 때, 다음과 같이 작성할 수 있다.

import { NextRequest } from "next/server";
import { ImageResponse } from "next/og";
import { join } from "path";
import { readFileSync } from "fs";

export async function GET(request: NextRequest) {
  // 생략...
  const fontPath = join(process.cwd(), "public", "fonts", "WONTitle.ttf");
  const fontData = readFileSync(fontPath);
  
  return new ImageResponse(
    (
      <div>
      // 생략...
      </div>
    ),
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: "WONTitle",
          data: fontData,
        },
      ],
    },
  );
}

이미지 추가하기

이미지를 불러와 추가할 수도 있다. 외부 URL이 존재하는 경우 그 URL을 그냥 그대로 넣으면 되고, 로컬에 있는 이미지를 사용할 경우 아래와 같이 사용할 수 있다.

const logoPath = join(
  process.cwd(),
  "public",
  "images",
  "logo",
  `${logo}.png`,
);
const logoBuffer = readFileSync(logoPath);
const logoBase64 = `data:image/png;base64,${logoBuffer.toString("base64")}`;

// ... 생략

return new ImageResponse((
  <img
    alt="og thumbnail"
    tw="absolute -translate-x-1/2 -translate-y-1/2 "
    src={logoBase64}
    width={logoWidth}
    height={logoHeight}
    style={{
      opacity: 0.6,
    }}
/>), ...)

결과

AnimatedImage.gif

이제 언어에 따라 스타일은 같지만, 텍스트는 다르게하여 썸네일을 동적으로 생성해 보여줄 수 있게 되었다. 매번 썸네일을 생성하기 위에 이미지 에디터에 들어가고는 했는데, 이제 그럴 일은 없을 것 같다.

🔎 참조

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0