메타데이터
Next.js에는 SEO 및 웹 공유성을 개선하기 위해 애플리케이션 메타데이터(예: HTML 헤드 요소 내부의 meta 및 link 태그)를 정의하는 데 사용할 수 있는 메타데이터 API가 있습니다.
애플리케이션에 메타데이터를 추가하는 방법에는 두 가지가 있습니다:
구성 기반 메타데이터: 정적 메타데이터 객체 또는 generateMetadata function을 layout.js 또는 page.js 파일로 내보냅니다.
파일 기반 메타데이터: 정적 또는 동적으로 생성된 특수 파일을 route 세그먼트에 추가합니다.
이 두 가지 옵션을 모두 사용하면 Next.js가 페이지에 대한 관련 <head> 요소를 자동으로 생성합니다. ImageResponse 생성자를 사용하여 동적 OG(OpenGrpah) 이미지를 생성할 수도 있습니다.
정적 메타데이터
정적 메타데이터를 정의하려면 layout.js 또는 정적 page.js 파일에서 메타데이터 객체를 내보내세요.
import { Metadata } from 'next';
export const metadata: Metadata = {
title: '...',
description: '...',
};
export default function Page() {}
사용 가능한 모든 옵션은 API 문서를 참조하세요.
동적 메타데이터
generateMetadata 함수를 사용하여 동적 값이 필요한 메타데이터를 가져올 수 있습니다.
import { 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> {
// read route params
const id = params.id;
// fetch data
const product = await fetch(`https://.../${id}`).then((res) => res.json());
// optionally access and extend (rather than replace) parent metadata
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) {}
사용 가능한 모든 매개변수는 API 레퍼런스를 참조하세요.
알아두면 좋습니다:
- generateMetadata를 통한 정적 메타데이터와 동적 메타데이터는 모두 서버 컴포넌트에서만 지원됩니다.
- route를 렌더링할 때 Next.js는 generateMetadata, generateStaticParams, 레이아웃, 페이지 및 서버 컴포넌트에서 동일한 데이터에 대한 가져오기 요청을 자동으로 중복 제거합니다. fetch를 사용할 수 없는 경우 React cache를 사용할 수 있습니다.
- Next.js는 클라이언트에 UI를 스트리밍하기 전에 generateMetadata 내부의 데이터 fetch가 완료될 때까지 기다립니다. 이렇게 하면 스트리밍된 응답의 첫 부분에 <head> 태그가 포함되도록 보장합니다.
파일 기반 메타데이터
메타데이터로 사용할 수 있는 특수 파일은 다음과 같습니다:
- favicon.ico, apple-icon.jpg, icon.jpg
- opengraph-image.jpg 및 twitter-image.jpg
- robots.txt
- sitemap.xml
이러한 파일을 정적 메타데이터에 사용하거나 코드를 사용하여 프로그래밍 방식으로 생성할 수 있습니다.
구현 방법과 예제는 메타데이터 파일 API 참조 및 동적 이미지 생성을 참조하세요.
동작
파일 기반 메타데이터가 더 높은 우선순위를 가지며 모든 구성 기반 메타데이터보다 우선합니다.
기본 필드
route가 메타데이터를 정의하지 않더라도 항상 추가되는 두 가지 기본 메타 태그가 있습니다:
메타 문자 집합 태그는 웹사이트의 문자 인코딩을 설정합니다.
메타 뷰포트 태그는 웹사이트의 뷰포트 너비와 배율을 설정하여 다양한 장치에 맞게 조정합니다.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
참고: 기본 viewport 메타 태그를 덮어쓸 수 있습니다.
순서
메타데이터는 root 세그먼트부터 시작하여 최종 page.js 세그먼트에 가장 가까운 세그먼트까지 순서대로 평가됩니다. 예를 들어
- app/layout.tsx(루트 레이아웃)
- app/blog/layout.tsx(중첩된 블로그 레이아웃)
- app/blog/[slug]/page.tsx(블로그 페이지)
병합
평가 순서에 따라 동일한 경로의 여러 세그먼트에서 내보낸 메타데이터 개체를 얕게 병합하여, 라우트 최종 메타데이터 출력을 형성합니다. 중복 키는 순서에 따라 교체됩니다.
즉, 이전 세그먼트에서 정의된 openGraph 및 robot과 같이 중첩된 필드가 있는 메타데이터는 마지막 세그먼트에서 해당 필드를 덮어쓰고 정의합니다.
필드 덮어쓰기
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/blog/page.js가 openGraph 메타데이터를 설정하기 때문에 app/blog/page.js의 모든 openGraph 필드가 app/blog/page.js에서 대체됩니다. openGraph.description이 없다는 점에 유의하세요.
세그먼트 간에 중첩된 일부 필드를 공유하면서 다른 필드를 덮어쓰려면 해당 필드를 별도의 변수로 가져올 수 있습니다:
// app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] };
// app/pages.js
import { openGraphImage } from './shared-metadata';
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Home',
},
};
// app/about/page.js
import { openGraphImage } from '../shared-metadata';
export const metadata = {
openGraph: {
...openGraphImage,
title: 'About',
},
};
위의 예시에서 OG 이미지는 app/layout.js와 app/about/page.js 간에 공유되지만 title은 다릅니다.
필드 상속
// app/layout.js
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
};
// app/about/page.js
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/pages.js는 openGraph 메타데이터를 설정하지 않기 때문에 app/layout.js의 모든 openGraph 필드는 app/about/page.js에서 상속됩니다.
동적 이미지 생성
ImageResponse 생성자를 사용하면 JSX 및 CSS를 사용하여 동적 이미지를 생성할 수 있습니다. 이는 오픈 그래프 이미지, 트위터 카드 등과 같은 소셜 미디어 이미지를 생성할 때 유용합니다.
ImageResponse는 edge 런타임을 사용하며, Next.js는 엣지에서 캐시된 이미지에 올바른 헤더를 자동으로 추가하여 성능을 개선하고 재계산을 줄입니다.
이를 사용하려면 next/server에서 ImageResponse를 가져오면 됩니다:
import { ImageResponse } from 'next/server';
// app/about/rout.jsx
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',
}}
>
Hello world!
</div>
),
{
width: 1200,
height: 600,
},
);
}
ImageResponse는 라우트 핸들러 및 파일 기반 메타데이터를 포함한 다른 Next.js API와 잘 통합됩니다. 예를 들어, opengraph-image.tsx 파일에서 ImageResponse를 사용하여 빌드 시점에 또는 요청 시점에 동적으로 오픈 그래프 이미지를 생성할 수 있습니다.
ImageResponse는 플렉스박스 및 절대 위치 지정, 사용자 정의 글꼴, 텍스트 줄 바꿈, 가운데 정렬, 중첩 이미지 등 일반적인 CSS 속성을 지원합니다. 지원되는 CSS 속성 전체 목록을 참조하세요.
알아두면 좋습니다:
- 예제는 vercel OG 플레이그라운드에서 확인할 수 있습니다.
- ImageResponse는 @vercel/og, Satori 및 Resvg를 사용하여 HTML과 CSS를 PNG로 변환합니다.
- 엣지 런타임만 지원됩니다. 기본 Node.js 런타임은 작동하지 않습니다.
- 플렉스박스와 CSS 속성의 하위 집합만 지원됩니다. 고급 레이아웃(예: 표시: 그리드)은 작동하지 않습니다.
- 최대 번들 크기는 500KB입니다. 번들 크기에는 JSX, CSS, 글꼴, 이미지 및 기타 모든 자산이 포함됩니다. 제한을 초과하는 경우 에셋의 크기를 줄이거나 런타임에 가져오는 것을 고려하세요.
- 글꼴 형식은 ttf, otf, woff만 지원됩니다. 글꼴 구문 분석 속도를 최대화하려면 woff보다 ttf 또는 otf가 선호됩니다.
JSON-LD
JSON-LD는 검색 엔진이 콘텐츠를 이해하는 데 사용할 수 있는 구조화된 데이터 형식입니다. 예를 들어 사람, 이벤트, 조직, 영화, 책, 레시피 및 기타 여러 유형의 엔티티를 설명하는 데 사용할 수 있습니다.
현재 JSON-LD에 대한 권장 사항은 layout.js 또는 page.js 컴포넌트에서 구조화된 데이터를 <script> 태그로 렌더링하는 것입니다. 예를 들어
// app/products/[id]/page.tsx
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>
{/* Add JSON-LD to your page */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
);
}
Google용 리치 결과 테스트 또는 일반 스키마 마크업 유효성 검사기를 사용하여 구조화된 데이터의 유효성을 검사하고 테스트할 수 있습니다.
schema-dts와 같은 커뮤니티 패키지를 사용하여 TypeScript로 JSON-LD를 입력할 수 있습니다:
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.',
};