메타데이터
Next.js에는 애플리케이션 메타데이터(예: HTML head
요소 내의 meta
및 link
태그)를 정의하는 데 사용할 수 있는 메타데이터 API가 있어 SEO와 웹 공유성을 향상시킬 수 있습니다.
애플리케이션에 메타데이터를 추가하는 두 가지 방법이 있습니다:
- 설정 기반 메타데이터:
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() {}
export const metadata = {
title: "...",
description: "...",
};
export default function Page() {}
사용 가능한 모든 옵션은 API 참조를 참조하세요.
동적 메타데이터
동적 값이 필요한 메타데이터를 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 참조를 참조하세요.
알아두면 좋은 점:
generateMetadata
를 통한 정적 및 동적 메타데이터는 서버 컴포넌트에서만 지원됩니다.fetch
요청은generateMetadata
,generateStaticParams
, 레이아웃, 페이지 및 서버 컴포넌트에서 동일한 데이터에 대해 자동으로 메모이제이션됩니다.fetch
를 사용할 수 없는 경우 Reactcache
를 사용할 수 있습니다.- Next.js는 클라이언트로 UI를 스트리밍하기 전에
generateMetadata
내부의 데이터 가져오기가 완료될 때까지 기다립니다. 이는 스트리밍된 응답의 첫 부분에<head>
태그가 포함되도록 보장합니다.
파일 기반 메타데이터
메타데이터에 사용할 수 있는 특별한 파일은 다음과 같습니다:
- favicon.ico, apple-icon.jpg, 및 icon.jpg
- opengraph-image.jpg 및 twitter-image.jpg
- robots.txt
- sitemap.xml
이러한 파일을 정적 메타데이터에 사용하거나 코드로 이러한 파일을 프로그래밍 방식으로 생성할 수 있습니다.
구현 및 예제는 메타데이터 파일 API 참조 및 동적 이미지 생성을 참조하세요.
동작
파일 기반 메타데이터가 더 높은 우선순위를 가지며 설정 기반 메타데이터를 덮어씁니다.
기본 필드
라우트에 메타데이터가 정의되어 있지 않더라도 항상 추가되는 두 개의 기본 meta
태그가 있습니다:
- meta charset 태그는 웹사이트의 문자 인코딩을 설정합니다.
- meta viewport 태그는 다양한 장치에 맞게 조정하기 위해 웹사이트의 뷰포트 너비와 스케일을 설정합니다.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
알아두면 좋은 점: 기본
viewport
meta 태그를 덮어쓸 수 있습니다.
순서
메타데이터는 루트 세그먼트에서 시작하여 최종 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 is a...",
},
};
export const metadata = {
title: "Blog",
openGraph: {
title: "Blog",
},
};
// 출력:
// <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
메타데이터를 설정하기 때문에app/blog/page.js
에서 대체됩니다.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
사이에 공유되지만 제목은 다릅니다.
필드 상속
export const metadata = {
title: "Acme",
openGraph: {
title: "Acme",
description: "Acme is a...",
},
};
export const metadata = {
title: "About",
};
// 출력:
// <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/layout.js
의 모든openGraph
필드는app/about/page.js
에서openGraph
메타데이터를 설정하지 않기 때문에app/about/page.js
에서 상속됩니다.
동적 이미지 생성
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
는 라우트 핸들러 및 파일 기반 메타데이터를 포함한 다른 Next.js API와 잘 통합됩니다. 예를 들어, opengraph-image.tsx
파일에서 ImageResponse
를 사용하여 빌드 시간이나 요청 시 동적으로 Open Graph 이미지를 생성할 수 있습니다.
ImageResponse
는 flexbox와 절대 위치 지정, 사용자 정의 폰트, 텍스트 줄 바꿈, 중앙 정렬 및 중첩 이미지를 포함한 일반적인 CSS 속성을 지원합니다. 지원되는 CSS 속성의 전체 목록을 참조하세요.
알아두면 좋은 점:
- 예제는 Vercel OG 플레이그라운드에서 확인할 수 있습니다.
ImageResponse
는 @vercel/og, Satori, 및 Resvg를 사용하여 HTML과 CSS를 PNG로 변환합니다.- Edge 런타임만 지원됩니다. 기본 Node.js 런타임은 작동하지 않습니다.
- flexbox와 CSS 속성의 일부만 지원됩니다. 고급 레이아웃(예:
display: grid
)은 작동하지 않습니다.- 최대 번들 크기는
500KB
입니다. 번들 크기에는 JSX, CSS, 폰트, 이미지 및 기타 자산이 포함됩니다. 제한을 초과하는 경우 자산의 크기를 줄이거나 런타임에 가져오는 것을 고려하세요.ttf
,otf
, 및woff
폰트 형식만 지원됩니다. 폰트 파싱 속도를 최대화하려면woff
보다ttf
또는otf
를 선호합니다.
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>
);
}
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 또는 일반 Schema Markup Validator를 사용하여 구조화된 데이터를 검증하고 테스트할 수 있습니다.
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.",
};