개요
블로그 게시글을 다국어로 제공하고 나서, 언어를 바꿀 때 텍스트는 바뀌지만 썸네일은 바뀌지 않아서 내용은 영어인데 이미지는 한글인 경우가 나타나 어색해 보이는 일이 있었다. 이를 이유로 이미지를 동적으로 생성하는 것이 필요하다 싶어 조사하던 중 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
사용자 정의 폰트 추가하기
더 멋진 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,
}}
/>), ...)
결과
이제 언어에 따라 스타일은 같지만, 텍스트는 다르게하여 썸네일을 동적으로 생성해 보여줄 수 있게 되었다. 매번 썸네일을 생성하기 위에 이미지 에디터에 들어가고는 했는데, 이제 그럴 일은 없을 것 같다.
🔎 참조