Metadata API 문법을 볼 때 먼저 확인할 포인트
이 문서는 Next.js App Router 환경에서 metadata API를 사용하는 올바른 방법을 단계별로 설명합니다. 먼저 기본적인 메타데이터 설정 방식을 이해한 뒤, 실제 포트폴리오 코드에 적용된 잘못된 예제를 살펴보고 그 문제점을 분석합니다. 이를 통해 왜 해당 방식이 문제가 되는지와, App Router에 맞는 올바른 메타데이터 구조를 어떻게 학습하고 적용해야 하는지를 함께 정리합니다.
이 글은 문법과 코드 예제 중심입니다
이 글의 검색 의도는 “Next.js Metadata API를 실제 코드에서 어떻게 쓰는가”입니다. metadata의 전체 설계 기준과 SEO 판단 기준은 Next.js App Router metadata 설정 기준을 대표 글로 두고, 이 글에서는 metadata 객체, generateMetadata, 동적 라우트 예제를 빠르게 확인할 수 있도록 역할을 분리합니다.
검색 결과가 이상하거나 OG 이미지·SSR까지 함께 봐야 한다면 Next.js SEO 현장 점검 체크리스트에서 증상별 확인 순서를 먼저 잡는 편이 좋습니다.
metadata 객체 구조와 적용 위치

Next.js App Router에서 metadata란, 브라우저와 검색 엔진, 그리고 SNS 크롤러가 페이지를 어떻게 인식해야 하는지를 정의하는 정보 집합입니다. 페이지 제목(title), 설명(description), 검색 엔진 노출 정보(SEO), 그리고 Open Graph·Twitter Card와 같은 공유 미리보기 정보가 여기에 포함됩니다.
SEO 목적의 메타데이터는 metadata API로 관리하는 것이 권장됩니다.
즉, “이 페이지는 어떤 메타 정보를 가진다”라고 객체 형태로 명확히 선언하면,
Next.js가 내부적으로 이를 해석하여 적절한 <head> 태그를 자동으로 생성합니다.
이 구조의 핵심 장점은 메타데이터 관리 위치가 명확해진다는 점입니다. 레이아웃 단위(layout.tsx)에서는 사이트 전체에 공통으로 적용되는 메타 정보를 정의하고, 페이지 단위(page.tsx)에서는 해당 페이지에만 필요한 메타 정보를 추가로 선언할 수 있습니다. Next.js는 이 정보를 자동으로 병합하여 최종 결과를 만들어 줍니다.
그 결과, 개발자는 SEO, Open Graph, Twitter Card를 각각 따로 신경 쓰며 head 태그를 직접 조작할 필요가 없어지고, 메타데이터를 하나의 일관된 방식으로 안전하게 관리할 수 있게 됩니다.
metadata 객체를 export하는 기본 방법
metadata를 넣는 가장 기본적인 방법은 layout.tsx 또는 page.tsx 파일 상단에서
export const metadata를 선언하는 것입니다.
이 객체는 “이 페이지가 어떤 정보로 설명되어야 하는지”를 정의하는 역할을 하며,
Next.js가 이를 기반으로 head 태그를 자동 생성합니다.
중요한 점은, metadata는 실제 렌더링 로직과 분리되어 관리된다는 점입니다. 컴포넌트 내부에서 meta 태그를 직접 작성하지 않고, 파일 단위로 메타 정보를 선언함으로써 구조가 명확해집니다.
layout.tsx에 정의한 metadata는 모든 하위 페이지에 공통으로 적용되며, page.tsx에 metadata를 추가로 선언하면 해당 페이지의 메타 정보만 덮어쓰는 방식으로 동작합니다. 이 병합 규칙 덕분에 페이지별 SEO 설정을 안전하게 관리할 수 있습니다.
import type { Metadata } from "next";
/**
* ✅ App Router 표준 metadata 구성 예제
* - SEO
* - Open Graph (카카오톡, 페이스북 등)
* - Twitter Card
* - 검색엔진 크롤링
* - 아이콘 / 앱 정보
*/
export const metadata: Metadata = {
/**
* 기준 URL
* - 상대 경로로 작성된 og:image, canonical 계산에 사용
*/
metadataBase: new URL("https://example.com"),
/**
* 기본 SEO 메타
*/
title: "STYNA - 깔끔한 스타일 쇼핑몰",
description: "최신 패션 트렌드를 만나보세요",
keywords: [
"패션 쇼핑몰",
"온라인 쇼핑",
"의류",
"STYNA",
"트렌드 패션",
],
/**
* 문서 작성자 / 발행자 정보
*/
authors: [{ name: "STYNA" }],
creator: "STYNA",
publisher: "STYNA",
/**
* 검색 엔진 크롤링 제어
*/
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
"max-video-preview": -1,
},
},
/**
* Open Graph
* - 카카오톡, 페이스북, 슬랙 미리보기
*/
openGraph: {
type: "website",
url: "https://example.com",
siteName: "STYNA",
title: "STYNA - 깔끔한 스타일 쇼핑몰",
description: "최신 패션 트렌드를 만나보세요",
locale: "ko_KR",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "STYNA 쇼핑몰 미리보기 이미지",
},
],
},
/**
* Twitter Card
*/
twitter: {
card: "summary_large_image",
title: "STYNA - 깔끔한 스타일 쇼핑몰",
description: "최신 패션 트렌드를 만나보세요",
images: ["/og-image.png"],
},
/**
* 아이콘 / 파비콘
*/
icons: {
icon: [
{ url: "/favicon.ico", sizes: "16x16" },
{ url: "/favicon.ico", sizes: "32x32" },
],
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180" }],
shortcut: "/favicon.ico",
},
/**
* 사이트 인증 (필요 시)
*/
verification: {
google: "google-site-verification-code",
other: {
"naver-site-verification": "naver-site-verification-code",
},
},
/**
* PWA / 앱 관련
*/
applicationName: "STYNA",
themeColor: "#000000",
};
위 예시는 App Router 환경에서 가장 많이 사용되는 기본 메타데이터 구성입니다. 실무에서는 프로젝트 규모와 목적에 따라 항목을 추가하거나 단순화할 수 있지만, 이 구조를 기준으로 이해하면 metadata API의 역할을 빠르게 파악할 수 있습니다.
App Router 프로젝트의 metadata 파일 구조
아래 코드는 실제 포트폴리오 프로젝트에서 사용하던 layout.tsx 구조를 메타데이터 학습 목적에 맞게 요약하여 정리한 예시입니다. metadata API를 사용하고 있음에도 불구하고, head 태그를 직접 작성하여 중복된 메타 관리가 발생하던 구조입니다.
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
display: "swap",
});
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: "STYNA - 깔끔한 스타일 쇼핑몰",
description: "최신 패션 트렌드를 만나보세요",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<head>
<meta name="description" content="최신 패션 트렌드" />
</head>
<body className={inter.variable}>{children}</body>
</html>
);
}
metadata 중복과 누락이 생기는 원인

metadata API와 head 수동 선언이 동시에 존재하면서 동일한 메타 정보가 중복 정의되고 있습니다. 이로 인해 크롤러는 어떤 메타 정보를 기준으로 해석해야 하는지 명확히 판단하기 어려워집니다.
generateMetadata로 동적 라우트 처리하기
해결 방법은 명확합니다. SEO, Open Graph, 아이콘, 테마 컬러와 같은 메타 정보는 모두 metadata API로 이동시키고, layout.tsxhead 태그에서는 불필요한 메타 선언을 제거합니다. 이렇게 하면 Next.js의 메타 병합 전략을 온전히 활용할 수 있습니다.
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
display: "swap",
});
// ✅ 모든 메타데이터는 metadata API에서만 관리
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: "STYNA - 깔끔한 스타일 쇼핑몰",
description: "최신 패션 트렌드를 만나보세요",
};
// ✅ RootLayout는 화면 구조만 담당 (head 직접 조작 ❌)
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<body className={inter.variable}>
{children}
</body>
</html>
);
}
metadata API 코드 예시 정리
Next.js App Router에서는 metadata API를 중심으로 메타데이터를 선언적으로 관리하는 것이 핵심입니다. 이 문서에서 살펴본 것처럼, 잘못된 구조를 직접 비교해 보면 왜 head 태그를 수동으로 조작하는 방식이 권장되지 않는지 명확히 이해할 수 있습니다. 올바른 metadata 사용 방식은 유지보수성과 SEO 안정성을 동시에 확보하는 기반이 됩니다.
metadata API의 전체 SEO 설정 흐름은 Next.js 메타데이터 API SEO 설정 글에서 함께 정리해두었습니다.
Metadata API FAQ
Q. metadata와 head를 같이 쓰면 안 되나요?
App Router에서는 metadata API가 head 생성을 담당하므로,
동일 목적의 메타 태그를 직접 작성하면 충돌이 발생할 수 있습니다.
Q. 파비콘도 metadata로 옮겨야 하나요?
아이콘, apple-touch-icon, shortcut icon 모두 metadata.icons에서 관리하는 것이 권장됩니다.
Q. 이 구조가 SEO에 바로 악영향을 주나요?
즉시 패널티가 발생하지는 않지만, 크롤러 해석 불일치로 인해
검색 결과 미리보기 품질이 저하될 수 있습니다.
metadata 객체 구조와 generateMetadata 코드 예시
이 글은 App Router의 Metadata API를 코드 관점에서 다룹니다. 개념 설명보다 어떤 파일에서 어떤 객체를 export하고, 동적 라우트에서 params를 받아 어떻게 metadata를 만드는지에 초점을 둡니다.
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: '게시글 목록 | BlogFlow',
description: '프론트엔드 개발 글을 주제별로 확인합니다.',
openGraph: {
title: '게시글 목록 | BlogFlow',
description: '프론트엔드 개발 글을 주제별로 확인합니다.',
url: 'https://blogflow.kr/posts',
type: 'website',
},
};
동적 라우트에서 generateMetadata 사용하기
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: '${post.title} | BlogFlow',
description: post.description,
alternates: { canonical: '/posts/${post.slug}' },
openGraph: {
title: post.title,
description: post.description,
type: 'article',
url: 'https://blogflow.kr/posts/${post.slug}',
},
};
}
Metadata API의 실무 설계 기준은 Next.js App Router metadata 설정 기준에서 확인하고, 전체 SEO 점검 흐름은 Next.js SEO 완전 가이드로 연결해 보면 좋습니다.
“Next.js Metadata API 실무 예제: metadata 객체와 generateMetadata 코드”에 대한 8개의 생각