Metadata
Next.js에는 SEO와 웹 공유성을 향상시키기 위해 애플리케이션 메타데이터(e.g. HTML head
요소 내의 meta
및 link
태그)를 정의하는 데 사용할 수 있는 Metadata API가 있습니다.
애플리케이션에 메타데이터를 추가하는 방법에는 두 가지가 있습니다:
- Config-based Metadata:
layout.js
또는page.js
파일에서 정적metadata
객체 또는 동적generateMetadata
함수를 내보냅니다. - File-based Metadata: 경로 세그먼트에 정적 또는 동적으로 생성된 특수 파일을 추가합니다.
이 두 옵션을 사용하면 Next.js가 페이지에 대한 관련 <head>
요소를 자동으로 생성합니다. 또한 ImageResponse
생성자를 사용하여 동적 OG 이미지를 만들 수 있습니다.
Static Metadata
정적 메타데이터를 정의하려면 layout.js
또는 정적 page.js
파일에서 Metadata
객체를 내보냅니다.
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
export const metadata = {
title: '...',
description: '...',
}
export default function Page() {}
사용 가능한 모든 옵션은 API Reference를 참조하세요.
Dynamic Metadata
동적 값이 필요한 메타데이터를 fetch
하기 위해 generateMetadata
함수를 사용할 수 있습니다.
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: { id: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata,
): Promise<Metadata> {
// 경로 매개변수 읽기
const id = params.id
// 데이터 가져오기
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// 부모 메타데이터를 확장하는 옵션 (대체하지 않고)
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}
export async function generateMetadata({ params, searchParams }, parent) {
// 경로 매개변수 읽기
const id = params.id
// 데이터 가져오기
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// 부모 메타데이터를 확장하는 옵션 (대체하지 않고)
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }) {}
사용 가능한 모든 매개변수는 API Reference를 참조하세요.
알아두면 좋은 점:
generateMetadata
를 통한 정적 및 동적 메타데이터는 서버 컴포넌트에서만 지원됩니다.fetch
요청은generateMetadata
,generateStaticParams
, 레이아웃, 페이지 및 서버 컴포넌트 전반에 걸쳐 동일한 데이터를 위해 자동으로 메모이제이션됩니다.fetch
를 사용할 수 없는 경우 Reactcache
를 사용할 수 있습니다.- Next.js는 UI를 클라이언트로 스트리밍하기 전에
generateMetadata
내부의 데이터 가져오기가 완료될 때까지 기다립니다. 이는 스트리밍된 응답의 첫 번째 부분에<head>
태그가 포함되도록 보장합니다.
File-based metadata
다음과 같은 특수 파일은 메타데이터에 사용할 수 있습니다:
- favicon.ico, apple-icon.jpg, 그리고 icon.jpg
- opengraph-image.jpg 및 twitter-image.jpg
- robots.txt
- sitemap.xml
이 파일들을 정적 메타데이터에 사용할 수 있으며, 코드로 프로그래밍 방식으로 생성할 수도 있습니다.
구현 및 예제는 Metadata Files API Reference와 Dynamic Image Generation을 참조하세요.
Behavior
파일 기반 메타데이터가 더 높은 우선순위를 가지며, config 기반 메타데이터를 덮어씁니다.
Default Fields
경로가 메타데이터를 정의하지 않아도 항상 추가되는 두 개의 기본 meta
태그가 있습니다:
- meta charset 태그 (opens in a new tab)는 웹사이트의 문자 인코딩을 설정합니다.
- meta viewport 태그 (opens in a new tab)는 웹사이트의 뷰포트 너비와 스케일을 설정하여 다양한 기기에 맞게 조정합니다.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
알아두면 좋은 점: 기본
viewport
메타 태그를 덮어쓸 수 있습니다.
Ordering
메타데이터는 루트 세그먼트에서 최종 page.js
세그먼트에 가장 가까운 세그먼트까지 순서대로 평가됩니다. 예를 들어:
app/layout.tsx
(루트 레이아웃)app/blog/layout.tsx
(중첩된 블로그 레이아웃)app/blog/[slug]/page.tsx
(블로그 페이지)
Merging
평가 순서에 따라 동일한 경로의 여러 세그먼트에서 내보낸 메타데이터 객체는 얕게 병합되어 경로의 최종 메타데이터 출력이 됩니다. 중복 키는 순서에 따라 교체됩니다.
이는 openGraph
및 robots
와 같은 중첩된 필드를 가진 메타데이터가 이전 세그먼트에 정의된 경우 마지막 세그먼트에 의해 덮어쓰여지는 것을 의미합니다.
Overwriting fields
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
export const metadata = {
title: 'Blog',
openGraph: {
title: 'Blog',
},
}
// Output:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />
위 예제에서:
app/layout.js
의title
은app/blog/page.js
의title
로 교체됩니다.app/layout.js
의 모든openGraph
필드는app/blog/page.js
에서openGraph
메타데이터를 설정하므로 교체됩니다.openGraph.description
이 없는 것을 확인하세요.
세그먼트 간에 일부 중첩된 필드를 공유하면서 다른 필드를 덮어쓰려면 별도의 변수로 분리할 수 있습니다:
export const openGraphImage = { images: ['http://...'] }
import { openGraphImage } from './shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Home',
},
}
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'About',
},
}
위 예제에서, OG 이미지는 app/layout.js
와 app/about/page.js
간에 공유되지만 제목은 다릅니다.
Inheriting fields
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
export const metadata = {
title: 'About',
}
// Output:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />
참고 사항
app/layout.js
의title
은app/about/page.js
의title
로 교체됩니다.app/about/page.js
가openGraph
메타데이터를 설정하지 않기 때문에app/layout.js
의 모든openGraph
필드는app/about/page.js
에서 상속됩니다.
Dynamic Image Generation
ImageResponse
생성자를 사용하여 JSX와 CSS를 사용하여 동적 이미지를 생성할 수 있습니다. 이는 Open Graph 이미지, Twitter 카드 등 소셜 미디어 이미지를 만드는 데 유용합니다.
이를 사용하려면 next/og
에서 ImageResponse
를 가져올 수 있습니다:
import { ImageResponse } from 'next/og'
export async function GET() {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello world!
</div>
),
{
width: 1200,
height: 600,
},
)
}
ImageResponse
는 Route Handlers 및 파일 기반 메타데이터를 포함한 다른 Next.js API와 잘 통합됩니다. 예를 들어, 빌드 타임 또는 요청 시 동적으로 Open Graph 이미지를 생성하기 위해 opengraph-image.tsx
파일에서 ImageResponse
를 사용할 수 있습니다.
ImageResponse
는 flexbox 및 절대 위치 지정, 사용자 정의 폰트, 텍스트 줄 바꿈, 중앙 정렬, 중첩된 이미지 등 일반적인 CSS 속성을 지원합니다. 지원되는 CSS 속성의 전체 목록을 확인하세요.
알아두면 좋은 점:
- 예제는 Vercel OG Playground (opens in a new tab)에서 확인할 수 있습니다.
ImageResponse
는 @vercel/og (opens in a new tab), Satori (opens in a new tab), 그리고 Resvg를 사용하여 HTML 및 CSS를 PNG로 변환합니다.- Edge Runtime만 지원됩니다. 기본 Node.js 런타임은 작동하지 않습니다.
- flexbox 및 일부 CSS 속성만 지원됩니다. 고급 레이아웃(e.g.
display: grid
)은 작동하지 않습니다.- 최대 번들 크기는
500KB
입니다. 번들 크기에는 JSX, CSS, 폰트, 이미지 및 기타 모든 자산이 포함됩니다. 제한을 초과하는 경우, 자산 크기를 줄이거나 런타임에 가져오는 것을 고려하세요.ttf
,otf
, 그리고woff
폰트 형식만 지원됩니다. 폰트 파싱 속도를 최대화하려면ttf
또는otf
가woff
보다 선호됩니다.
JSON-LD
JSON-LD (opens in a new tab)는 검색 엔진이 콘텐츠를 이해할 수 있도록 하는 구조화된 데이터 형식입니다. 예를 들어, 사람, 이벤트, 조직, 영화, 책, 레시피 등 다양한 유형의 엔티티를 설명하는 데 사용할 수 있습니다.
현재 JSON-LD에 대한 권장 사항은 layout.js
또는 page.js
컴포넌트에서 <script>
태그로 구조화된 데이터를 렌더링하는 것입니다. 예를 들어:
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* 페이지에 JSON-LD 추가 */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* 페이지에 JSON-LD 추가 */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
Google의 Rich Results Test (opens in a new tab) 또는 일반적인 Schema Markup Validator (opens in a new tab)를 사용하여 구조화된 데이터를 검증하고 테스트할 수 있습니다.
TypeScript를 사용하여 JSON-LD를 입력하려면 schema-dts
(opens in a new tab)와 같은 커뮤니티 패키지를 사용할 수 있습니다:
import { Product, WithContext } from 'schema-dts'
const jsonLd: WithContext<Product> = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Next.js Sticker',
image: 'https://nextjs.org/imgs/sticker.png',
description: 'Dynamic at the speed of static.',
}