Next.js Metadata & OG Images 정리
Next.js Docs – Getting Started: Metadata and OG images 페이지를 기반으로, App Router 환경에서의 메타데이터 및 OG(Open Graph) 이미지 설정 방법을 정리한 문서입니다.
1. 메타데이터 API 개요
Next.js의 Metadata API는 SEO와 소셜 공유(웹 공유성)를 개선하기 위해 페이지의 <head> 정보를 선언적으로 관리할 수 있게 해 줍니다.
주요 구성 요소는 다음과 같습니다.
- 정적 메타데이터 객체 (
metadata) - 동적 메타데이터 함수 (
generateMetadata) - 파일 기반(file-based) 메타데이터
- 파비콘, Open Graph 이미지, Twitter 카드 이미지, robots.txt, sitemap.xml 등
이 옵션들을 활용하면 Next.js가 페이지에 필요한 <meta>, <link> 태그 등을 자동으로 생성합니다.
metadata 객체와 generateMetadata 함수는 Server Component에서만 사용할 수 있습니다.
2. 기본 meta 태그 (Default fields)
어떠한 메타데이터도 정의하지 않아도, Next.js는 아래 두 개의 기본 메타 태그를 항상 추가합니다.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
charset: 문서 인코딩 설정 (UTF-8)viewport: 반응형 레이아웃을 위한 뷰포트 너비·배율 설정
나머지 메타데이터 필드는 Metadata 객체(정적) 또는 generateMetadata 함수(동적)를 통해 정의합니다.
3. 정적 메타데이터 (Static metadata)
정적 메타데이터는 정적인 layout.tsx 또는 page.tsx 파일에서 metadata 객체를 export 하는 방식으로 정의합니다.
// app/blog/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: 'A simple blog built with Next.js App Router.',
}
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
title,description외에도openGraph,twitter,robots,alternates등 다양한 필드를 설정할 수 있습니다.layout.tsx의 메타데이터는 해당 레이아웃을 사용하는 모든 하위 페이지에 상속되며,
하위 페이지에서metadata를 정의하면 해당 부분만 덮어쓰기(override) 할 수 있습니다.
정적 메타데이터는 빌드 시점에 평가되며, 데이터 fetch 없이 고정된 값을 사용합니다.
4. 동적 메타데이터 (generateMetadata)
페이지의 메타데이터가 외부 데이터나 동적 파라미터에 의존할 경우,
generateMetadata 함수를 사용하여 런타임에 메타데이터를 생성할 수 있습니다.
// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
type PageProps = {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params }: PageProps,
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params
// 1) 게시글 정보 가져오기
const post = await fetch(`https://api.example.com/posts/${slug}`).then((res) =>
res.json()
)
// 2) 가져온 데이터로 메타데이터 구성
return {
title: post.title,
description: post.summary ?? post.excerpt,
}
}
export default async function BlogPostPage({ params }: PageProps) {
const { slug } = await params
// ... 실제 페이지 렌더링
return <div>Post: {slug}</div>
}
generateMetadata는 Server Component 환경에서 실행됩니다.params,searchParams를 통해 동적 라우트 파라미터와 쿼리 스트링을 사용할 수 있습니다.- 두 번째 인자
parent(타입:ResolvingMetadata)를 통해 상위 레이아웃 메타데이터를 참조하고, 필요한 값을 재사용하거나 수정할 수 있습니다.
권장 패턴: 데이터 fetch가 필요할 경우, 페이지 본문과 메타데이터에서 동일한 fetch 로직을 공유하기 위해 아래와 같은
cache패턴을 함께 사용하는 것이 좋습니다. (5번 섹션 참고)
5. 스트리밍 메타데이터 (Streaming metadata)
동적으로 렌더링되는 페이지의 경우, Next.js는 UI 렌더링을 막지 않고 메타데이터를 스트리밍할 수 있습니다.
generateMetadata가 비동기 작업(예:fetch)을 수행해도,- 먼저 페이지의 시각적 콘텐츠를 스트리밍으로 렌더링한 뒤,
- 이후
generateMetadata가 완료되면<head>에 메타데이터를 주입합니다.
- 따라서 사용자는 페이지를 더 빨리 볼 수 있고, 메타데이터는 나중에 합류합니다.
단, 다음과 같은 봇/크롤러(예: Twitterbot, Slackbot, Bingbot)에게는 스트리밍 메타데이터가 비활성화됩니다.
- 이들은
<head>안에 메타 태그가 모두 준비되어 있다고 가정하기 때문에, - 해당 User-Agent에 대해서는 메타데이터가 blocking 방식으로 먼저 렌더링됩니다.
- 이 동작은
next.config.js의htmlLimitedBots옵션으로 커스터마이징하거나 비활성화할 수 있습니다.
정적으로 렌더링되는 페이지(SSG)는 빌드 시점에 메타데이터가 이미 결정되므로 스트리밍이 필요 없습니다.
6. 데이터 요청 메모이제이션 (Memoizing data requests)
메타데이터와 페이지 본문이 동일한 데이터를 필요로 하는 경우,
각각에서 동일한 API를 호출하면 중복 fetch가 발생할 수 있습니다.
이를 피하기 위해, React의 cache 함수를 사용하여 데이터 요청 결과를 메모이제이션할 수 있습니다.
// app/lib/posts.ts
import { cache } from 'react'
import { db } from '@/app/lib/db'
export const getPost = cache(async (slug: string) => {
const post = await db.query.posts.findFirst({
where: (table, { eq }) => eq(table.slug, slug),
})
return post
})
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { getPost } from '@/app/lib/posts'
type PageProps = { params: { slug: string } }
export async function generateMetadata(
{ params }: PageProps
): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post?.title ?? 'Post not found',
description: post?.description,
}
}
export default async function BlogPostPage({ params }: PageProps) {
const post = await getPost(params.slug)
if (!post) return <div>Post not found</div>
return <article>{post.title}</article>
}
getPost는 내부적으로 한 번만 실행되며,generateMetadata와 페이지 컴포넌트에서 같은 결과를 공유합니다.
- 네트워크 요청 및 DB 쿼리 수를 줄여 성능을 개선할 수 있습니다.
7. 파일 기반 메타데이터 (File-based metadata)
특정 파일 이름을 사용하면, Next.js가 자동으로 이를 메타데이터용 특수 파일로 인식합니다.
대표적인 파일들은 다음과 같습니다.
favicon.ico,apple-icon.png,icon.png(파비콘 및 앱 아이콘)opengraph-image.jpg,twitter-image.jpg(OG 이미지 & Twitter 카드 이미지)robots.txtsitemap.xml
이 파일들은 다음과 같이 사용할 수 있습니다.
- 정적 파일로 추가하여 바로 사용
- 또는 코드로 동적 생성(Route Handlers /
ImageResponse등 활용)
파일 위치에 따라 적용 범위(scope) 가 달라지며, 더 깊은 경로의 파일이 우선합니다.
8. 파비콘(Favicons)
파비콘은 브라우저 탭, 북마크, 검색 결과 등에서 사이트를 대표하는 작은 아이콘입니다.
app폴더의 루트에favicon.ico파일을 추가하면,- Next.js가 자동으로 이 파일을 파비콘으로 인식합니다.
app/
favicon.ico
layout.tsx
page.tsx
- 추가적으로
icon.png,apple-icon.png등 다양한 플랫폼용 아이콘도 파일 컨벤션에 맞게 둘 수 있습니다. - 코드로 파비콘을 동적으로 생성하는 것도 가능합니다. (별도 Favicon 관련 문서 참고)
9. 정적 Open Graph 이미지 (Static OG images)
Open Graph(OG) 이미지는 소셜 미디어나 메신저에서 링크를 공유할 때 표시되는 썸네일 이미지입니다.
9.1 루트 OG 이미지
app폴더 루트에opengraph-image.jpg파일을 추가하면,- 사이트 전체의 기본 OG 이미지로 사용됩니다.
app/
opengraph-image.jpg // 사이트 기본 OG 이미지
layout.tsx
page.tsx
9.2 라우트별 OG 이미지
특정 라우트에만 다른 OG 이미지를 사용하고 싶다면, 해당 폴더 안에 opengraph-image.jpg를 추가합니다.
app/
opengraph-image.jpg // 전체 기본 OG 이미지
blog/
page.tsx
opengraph-image.jpg // /blog 전용 OG 이미지
/blog페이지를 공유하면app/blog/opengraph-image.jpg가 사용됩니다.- 폴더 구조가 더 깊어질수록 가장 구체적인 경로의 파일이 우선 적용됩니다.
.jpeg,.png,.gif등 다른 이미지 포맷도 지원됩니다.
Twitter 전용 이미지는
twitter-image.jpg(또는.png,.gif) 파일 컨벤션으로 동일하게 설정할 수 있습니다.
10. 동적 Open Graph 이미지 (Generated OG images)
정적 이미지 파일 대신, 데이터에 따라 달라지는 OG 이미지를 동적으로 생성할 수도 있습니다.
이를 위해 next/og 패키지의 **ImageResponse**를 사용합니다.
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/posts'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default async function OGImage({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)
return new ImageResponse(
(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
background: 'white',
fontSize: 96,
}}
>
{post?.title ?? 'My Blog'}
</div>
),
{
width: size.width,
height: size.height,
}
)
}
opengraph-image.tsx파일은 특수 Route Handler로 동작하며,- JSX와 간단한 CSS 스타일을 사용해 이미지를 생성합니다.
- 지원 특징
- flexbox, 일부 CSS 속성
- 커스텀 폰트 로딩
- 텍스트 줄바꿈, 정렬, 중첩 이미지 등
- 내부적으로는
@vercel/og,satori,resvg를 사용해 HTML/CSS를 PNG로 변환합니다.
고급 레이아웃(grid 등)은 지원되지 않을 수 있으므로, 공식 문서의 “지원 CSS 목록”을 참고하는 것이 좋습니다.