Next.js의 App Router는 강력한 메타데이터 API를 제공합니다. 정적인 페이지는 export const metadata를 사용하면 되지만, 블로그 포스트, 상품 상세 페이지, 연예인 프로필 등 동적으로 변하는 페이지의 SEO를 극대화하려면 generateMetadata가 필수입니다.
정적 vs 동적 메타데이터
-
정적 메타데이터 (
metadata): 고정된 페이지(홈, 소개, 문의 등)에 사용합니다. -
동적 메타데이터 (
generateMetadata): URL 파라미터나 외부 API 데이터(DB)에 따라 메타데이터가 변경되어야 하는 페이지에 사용합니다.
generateMetadata 기본 구현 패턴
동적 라우트([id] 또는 [slug])에서 파라미터를 받아와 메타데이터를 생성하는 가장 표준적인 구조입니다.
// app/posts/[slug]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
interface Props {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
// 1. generateMetadata 함수 구현
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// Promise로 넘어오는 params 처리
const { slug } = await params;
// 외부 API 또는 DB에서 데이터 조회
const response = await fetch(`https://api.example.com/posts/${slug}`);
const post = await response.json();
// (선택사항) 부모 레이아웃의 메타데이터를 가져와 조합할 수도 있습니다.
const previousImages = (await parent).openGraph?.images || [];
return {
title: `${post.title} | 서비스명`,
description: post.summary,
openGraph: {
title: post.title,
description: post.summary,
url: `https://example.com/posts/${slug}`,
siteName: '서비스명',
images: [post.thumbnailUrl, ...previousImages],
type: 'article',
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.summary,
images: [post.thumbnailUrl],
},
};
}
// 2. 실제 페이지 컴포넌트
export default async function Page({ params }: Props) {
const { slug } = await params;
return <main>... {slug} 포스트 내용 ...</main>;
}
Next.js 15+ 변경 사항 적용: Next.js 15부터는
params와searchParams가 Promise 형태로 제공되므로, 내부에서await를 사용해 언랩(Unwrap)해야 타입을 정확히 맞출 수 있습니다.
