메타데이터
- Published on
- 모든 레이아웃이나 페이지에서 메타데이터를 정의하기 위해 메타데이터 API를 사용하세요.
Next.js는 SEO와 웹 공유성을 향상시키기 위해 HTML head
요소 내부의 meta
및 link
태그 같은 애플리케이션 메타데이터를 정의하는 데 사용할 수 있는 Metadata API를 제공합니다.
애플리케이션에 메타데이터를 추가하는 두 가지 방법이 있습니다.
- 설정 기반 메타데이터:
layout.js
또는page.js
파일에서 정적metadata
객체나 동적generateMetadata
함수를 내보냅니다. - 파일 기반 메타데이터: 라우트 세그먼트에 정적 또는 동적으로 생성된 특별한 파일을 추가합니다.
이 두 옵션을 사용하면 Next.js는 페이지에 대한 관련 <head>
요소를 자동으로 생성합니다. 또한 ImageResponse
생성자를 사용하여 동적 OG 이미지도 생성할 수 있습니다.
정적 메타데이터
정적 메타데이터를 정의하려면 layout.js
또는 정적 page.js
파일에서 Metadata
객체를 내보냅니다.
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
JavaScript
export const metadata = {
title: '...',
description: '...',
}
export default function Page() {}
사용 가능한 모든 옵션은 API 참조에서 확인할 수 있습니다.
동적 메타데이터
generateMetadata
함수를 사용하여 동적 값을 요구하는 메타데이터를 fetch
할 수 있습니다.
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) {}
JavaScript
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 참조에서 확인하세요.
generateMetadata
를 통한 정적 및 동적 메타데이터는 Server Components에서만 지원됩니다.fetch
요청은generateMetadata
,generateStaticParams
, Layouts, Pages 및 Server Components의 동일한 데이터에 대해 자동으로 메모화됩니다.fetch
를 사용할 수 없는 경우 Reactcache
를 사용할 수 있습니다.- Next.js는 클라이언트에 UI를 스트리밍하기 전에
generateMetadata
내의 데이터 가져오기가 완료될 때까지 기다립니다. 이렇게 하면 스트리밍된 응답의 첫 번째 부분에<head>
태그가 포함됩니다.
파일 기반 메타데이터
메타데이터를 위해 사용할 수 있는 특별한 파일들은 다음과 같습니다.
이들 파일들을 정적 메타데이터로 사용하거나 코드로 이 파일들을 프로그래밍적으로 생성할 수 있습니다.
구현과 예제는 메타데이터 파일 API 참조 및 동적 이미지 생성에서 확인하세요.
동작
파일 기반 메타데이터는 더 높은 우선순위를 가지며, 설정 기반 메타데이터를 덮어씁니다.
기본 필드
메타데이터를 정의하지 않는 경로에도 항상 추가되는 두 개의 기본 meta
태그가 있습니다.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
알아두면 좋은 점: 기본
viewport
메타 태그를 덮어쓸 수 있습니다.
순서
메타데이터는 최종 page.js
세그먼트에 가장 가까운 세그먼트로부터 루트 세그먼트까지 순서대로 평가됩니다. 예를 들면 아래와 같습니다.
app/layout.tsx
(루트 레이아웃)app/blog/layout.tsx
(중첩된 블로그 레이아웃)app/blog/[slug]/page.tsx
(블로그 페이지)
병합
평가 순서를 따라 같은 경로의 여러 세그먼트에서 내보낸 메타데이터 객체는 경로의 최종 메타데이터 출력물을 형성하기 위해 얕게 병합됩니다. 중복 키는 순서에 따라 대체됩니다.
이것은 openGraph
와 robots
와 같이 중첩된 필드를 가진 메타데이터가 이전 세그먼트에서 정의되면 마지막 세그먼트에서 덮어쓰게 됨을 의미합니다.
필드 덮어쓰기 예제 코드는 다음과 같습니다.
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme는 ...입니다.',
},
}
JavaScript
export const metadata = {
title: '블로그',
openGraph: {
title: '블로그',
},
}
// 출력:
// <title>블로그</title>
// <meta property="og:title" content="블로그" />
위 예제는 아래와 같이 동작됩니다.
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: '홈',
},
}
JavaScript
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: '소개',
},
}
위 예제에서 OG 이미지는 app/layout.js
와 app/about/page.js
사이에서 공유되며 타이틀은 다릅니다.
필드 상속
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme는 ...입니다.',
},
}
export const metadata = {
title: '소개',
}
// 출력:
// <title>소개</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme는 ...입니다." />
주의사항은 다음과 같습니다.
app/layout.js
의title
은app/about/page.js
의title
로 대체됩니다.app/about/page.js
가openGraph
메타데이터를 설정하지 않기 때문에app/layout.js
의 모든openGraph
필드가 상속됩니다.
동적 이미지 생성
ImageResponse
생성자를 사용하면 JSX와 CSS를 이용하여 동적 이미지를 생성할 수 있습니다. 이는 Open Graph 이미지, Twitter 카드 등의 소셜 미디어 이미지를 생성하기에 유용합니다.
ImageResponse
는 Edge Runtime을 사용하며, Next.js는 자동으로 엣지에서 캐싱된 이미지에 올바른 헤더를 추가하여 성능을 향상시키고 다시 계산을 줄입니다.
이를 사용하려면 next/server
에서 ImageResponse
를 가져올 수 있습니다.
import { ImageResponse } from 'next/server'
export const runtime = 'edge'
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',
}}
>
안녕, 세상아!
</div>
),
{
width: 1200,
height: 600,
}
)
}
ImageResponse
는 Route Handlers 및 파일 기반 메타데이터를 포함한 다른 Next.js API와 잘 통합됩니다. 예를 들어, opengraph-image.tsx
파일에서 ImageResponse
를 사용하여 빌드 시간 또는 요청 시간에 동적으로 Open Graph 이미지를 생성할 수 있습니다.
ImageResponse
는 flexbox와 절대 위치 지정, 사용자 정의 폰트, 텍스트 줄 바꿈, 중앙 정렬 및 중첩된 이미지를 포함한 일반적인 CSS 속성을 지원합니다. 지원되는 CSS 속성의 전체 목록을 확인하십시오.
- 예제는 Vercel OG Playground에서 확인할 수 있습니다.
ImageResponse
는 HTML과 CSS를 PNG로 변환하기 위해 @vercel/og, Satori, 및 Resvg를 사용합니다.- 오직 Edge Runtime만 지원됩니다. 기본 Node.js 런타임은 작동하지 않습니다.
- flexbox와 CSS 속성의 부분 집합만 지원됩니다. 고급 레이아웃(예:
display: grid
)은 작동하지 않습니다.- 번들 최대 크기는
500KB
입니다. 번들 크기에는 JSX, CSS, 폰트, 이미지 및 기타 모든 자산이 포함됩니다. 제한을 초과하는 경우 자산의 크기를 줄이거나 런타임에 가져올 수 있습니다.ttf
,otf
, 및woff
폰트 형식만 지원됩니다. 폰트 파싱 속도를 최대화하기 위해ttf
또는otf
가woff
보다 선호됩니다.
JSON-LD
JSON-LD는 검색 엔진이 귀하의 콘텐츠를 이해하는 데 사용할 수 있는 구조화된 데이터 형식입니다. 예를 들면, 이를 사용하여 사람, 이벤트, 조직, 영화, 책, 레시피 등 많은 종류의 엔터티를 설명할 수 있습니다.
현재 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>
)
}
JavaScript
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>
)
}
구조화된 데이터를 Rich Results Test로 Google에 대하여 검증하고 테스트하거나 일반적인 Schema Markup Validator를 사용할 수 있습니다.
타입스크립트를 사용하여 schema-dts
와 같은 커뮤니티 패키지로 JSON-LD를 타입화 할 수 있습니다.
import { Product, WithContext } from 'schema-dts'
const jsonLd: WithContext<Product> = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Next.js 스티커',
image: 'https://nextjs.org/imgs/sticker.png',
description: '정적의 속도에서 동적으로.',
}