Nextjs 14에서 Metadata 활용하는 방법

Nextjs 14에서 Metadata 활용하는 방법
Cozy CodingPosted On Jul 23, 20248 min read

메타데이터

Next.js에는 개선된 SEO와 웹 공유 가능성을 위해 응용 프로그램 메타데이터(예: HTML head 요소 내의 메타 및 링크 태그)를 정의하는 데 사용할 수있는 메타데이터 API가 있습니다.

응용 프로그램에 메타데이터를 추가하는 두 가지 방법이 있습니다:

  • 구성 기반 메타데이터: layout.js 또는 page.js 파일에 정적 메타데이터 개체 또는 동적 generateMetadata 함수를 내보냅니다.
  • 파일 기반 메타데이터: 라우트 세그먼트에 정적 또는 동적으로 생성된 특수 파일을 추가하십시오.

두 가지 옵션을 사용하면 Next.js가 페이지에 해당하는 head 요소를 자동으로 생성합니다. ImageResponse 생성자를 사용하여 동적 OG 이미지를 생성할 수도 있습니다.

정적 메타데이터

정적 메타데이터를 정의하려면 layout.js 또는 static page.js 파일에서 Metadata 객체를 내보내세요.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

아래 API 레퍼런스에서 모든 가능한 옵션을 확인할 수 있습니다.

동적 메타데이터

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> {
  // route 파라미터 읽기
  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) {}

API 참조에서 사용 가능한 모든 매개변수를 참조하세요.

알아두세요: 정적 및 동적 메타데이터는 generateMetadata를 통해 서버 컴포넌트에서만 지원됩니다. 동일한 데이터에 대한 fetch 요청은 generateMetadata, generateStaticParams, 레이아웃, 페이지 및 서버 컴포넌트 간에 자동으로 메모되어 있습니다. fetch가 사용 불가능한 경우 React 캐시를 사용할 수 있습니다. Next.js는 클라이언트로 UI를 스트리밍하기 전에 generateMetadata 내에서 데이터 가져오기가 완료될 때까지 기다립니다. 이는 스트리밍 응답의 첫 부분이 head 태그를 포함하는 것을 보장합니다.

파일 기반 메타데이터

이 특별한 파일들은 메타데이터로 사용할 수 있습니다:

  • favicon.ico, apple-icon.jpg, 그리고 icon.jpg
  • opengraph-image.jpg 와 twitter-image.jpg
  • robots.txt
  • sitemap.xml

이 파일들은 정적 메타데이터로 사용하거나 코드로 프로그래밍하여 동적으로 생성할 수 있습니다.

구현 및 예제는 메타데이터 파일 API 레퍼런스와 동적 이미지 생성을 참조하세요.

동작

파일 기반 메타데이터가 더 높은 우선 순위를 가지며 설정 기반 메타데이터를 재정의합니다.

기본 필드

라우트가 메타데이터를 정의하지 않더라도 항상 추가되는 두 가지 기본 메타 태그가 있습니다:

  • 메타 charset 태그는 웹사이트의 문자 인코딩을 설정합니다.
  • 메타 viewport 태그는 웹사이트의 뷰포트 너비와 확대 정도를 설정하여 다양한 기기에 대응하도록 합니다.

좋은 정보: 기본 viewport 메타 태그를 덮어쓸 수 있습니다.

주문

메타데이터는 순서대로 평가되며, 루트 세그먼트에서 시작하여 최종 페이지.js 세그먼트에 제일 가까운 세그먼트까지 내려갑니다. 예를 들어:

  • app/layout.tsx (Root Layout)
  • app/blog/layout.tsx (Nested Blog Layout)
  • app/blog/[slug]/page.tsx (Blog Page)

병합

평가 순서를 따라 동일 경로의 여러 세그먼트에서 내보낸 Metadata 객체들은 얕게 병합되어 경로의 최종 메타데이터 출력을 형성합니다. 중복 키는 순서에 따라 대체됩니다.

이는 이전 세그먼트에서 정의된 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의 제목이 app/blog/page.js의 제목으로 교체되었습니다.
  • app/layout.js의 모든 OpenGraph 필드가 app/blog/page.js로 대체되었습니다. 왜냐하면 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 간에 공유되지만 제목은 다릅니다.

필드 상속

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 필드가 상속됩니다.

동적 이미지 생성

ImageResponse 생성자를 사용하면 JSX 및 CSS를 사용하여 동적 이미지를 생성할 수 있습니다. Open Graph 이미지, Twitter 카드 등 소셜 미디어 이미지를 만드는 데 유용합니다.

ImageResponse는 Edge Runtime을 사용하며 Next.js는 캐시된 이미지에 올바른 헤더를 자동으로 추가하여 성능을 향상시키고 재계산을 줄여줍니다.

사용법은 next/og에서 ImageResponse를 가져와서 사용합니다:

import { ImageResponse } from 'next/og'
 
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은 빌드 시간에 또는 요청 시간에 Open Graph 이미지를 생성하기 위해 opengraph-image.tsx 파일에서 사용할 수 있습니다. ImageResponse는 flexbox와 absolute positioning을 포함한 일반적인 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 글꼴 형식만 지원됩니다. 글꼴 구문 분석 속도를 최대화하려면 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>
  )
}

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 스티커',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: '정적인 속도로 동적입니다.',
}