Next.js의 Metadata API를 사용하면 SEO와 웹 공유성을 높이기 위한 애플리케이션 메타데이터를 정의할 수 있습니다. 주요 방식은 다음과 같습니다:
- 정적 metadata 객체
- 동적 generateMetadata 함수
- favicon, OG 이미지 등 특수 파일 기반 메타데이터
이러한 옵션을 사용하면 Next.js가 자동으로 <head>
태그를 생성하며, 브라우저 개발자 도구에서 확인할 수 있습니다.
기본 필드(Default fields)
모든 라우트에 기본적으로 추가되는 두 개의 meta
태그가 있습니다:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
기타 메타데이터 필드는 Metadata
객체(정적) 또는 generateMetadata
함수(동적)로 정의할 수 있습니다.
정적 메타데이터(Static metadata)
정적 메타데이터를 정의하려면, 정적 layout.js 또는 page.js 파일에서 Metadata 객체를 export하세요. 예시:
// app/blog/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Blog',
description: '...',
}
export default function Page() {}
사용 가능한 모든 옵션은 generateMetadata 문서에서 확인할 수 있습니다.
동적 메타데이터(Generated metadata)
데이터에 따라 동적으로 메타데이터를 생성하려면 generateMetadata
함수를 사용하세요. 예시:
// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const slug = (await params).slug
// 포스트 정보 패칭
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) => res.json())
return {
title: post.title,
description: post.description,
}
}
export default function Page({ params, searchParams }: Props) {}
Next.js는 UI와 별도로 메타데이터를 스트리밍하여, 메타데이터가 준비되는 즉시 HTML에 삽입합니다.
데이터 요청 메모이징(Memoizing data requests)
메타데이터와 페이지 본문에서 동일한 데이터를 패칭해야 할 때, React의 cache
함수를 사용해 중복 요청을 방지할 수 있습니다.
// app/lib/data.ts
import { cache } from 'react'
import { db } from '@/app/lib/db'
// getPost는 두 번 호출되어도 실제로는 한 번만 실행됨
export const getPost = cache(async (slug: string) => {
const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
return res
})
// app/blog/[slug]/page.tsx
import { getPost } from '@/app/lib/data'
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.description,
}
}
export default async function Page({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return <div>{post.title}</div>
}
파일 기반 메타데이터(File-based metadata)
아래와 같은 특수 파일을 통해 메타데이터를 정의할 수 있습니다:
- favicon.ico, apple-icon.jpg, icon.jpg
- opengraph-image.jpg, twitter-image.jpg
- robots.txt
- sitemap.xml
이 파일들은 정적 메타데이터로 사용할 수도 있고, 코드로 동적으로 생성할 수도 있습니다.
파비콘(Favicons)
파비콘은 북마크, 검색 결과 등에서 사이트를 대표하는 작은 아이콘입니다. favicon.ico
파일을 app 폴더 루트에 추가하세요.
코드로 파비콘을 동적으로 생성할 수도 있습니다. 자세한 내용은 favicon 공식 문서 참고.
정적 OG 이미지(Static Open Graph images)
OG(Open Graph) 이미지는 소셜 미디어에서 사이트를 대표하는 이미지입니다. opengraph-image.png
파일을 app 폴더 루트에 추가하세요. 특정 라우트에만 적용하려면 해당 폴더에 이미지를 추가하면 됩니다. 더 구체적인 경로의 이미지가 우선 적용됩니다.
jpeg, png, webp 등 다양한 포맷을 지원합니다. 자세한 내용은 OG 이미지 공식 문서 참고.
동적 OG 이미지(Generated Open Graph images)
ImageResponse
생성자를 사용하면 JSX와 CSS로 동적 이미지를 생성할 수 있습니다. 예시:
// app/blog/[slug]/opengraph-image.ts
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'
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={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{post.title}
</div>
)
}
ImageResponse
는 flexbox, absolute positioning, 커스텀 폰트, 텍스트 래핑, 이미지 중첩 등 주요 CSS 속성을 지원합니다. (고급 레이아웃은 미지원)
참고:
- 예제는 Vercel OG Playground에서 확인할 수 있습니다.
ImageResponse
는 @vercel/og, satori, resvg를 사용해 HTML/CSS를 PNG로 변환합니다.
API Reference
이 페이지에서 언급된 주요 API는 아래에서 더 자세히 확인할 수 있습니다.
- generateMetadata
- generateViewport
- ImageResponse
- 메타데이터 파일 규칙
- favicon, icon, apple-icon
- opengraph-image, twitter-image
- robots.txt
- sitemap.xml
출처: https://nextjs.org/docs/app/getting-started/metadata-and-og-images