[Next.js 15] 동적으로 메타 태그 생성하기 Dynamic Metadata

Dynamic Metadata가 필요한 경우

검색 페이지에서 검색어에 따라 `title` 항목이 `{검색어} 검색 결과 | 웹소설 캘린더`라는 동적인 메타데이터를 생성하고 싶었다. 이처럼 페이지의 콘텐츠나 URL 파라미터에 따라 메타데이터가 변경되어야 하는 경우에는 동적 메타데이터 생성이 필요하다.

 

Next.js의 Metadata 생성 방법

Next.js는 SEO를 개선하기 위해 애플리케이션 메타데이터(`head` 요소 내부의 `meta` 및 `link` 태그)를 자동으로 생성해 주는 API를 제공한다.

 

Static Metadata (정적 메타데이터)

// layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}
  • 정적 메타데이터를 정의하기 위해서는 `layout.ts`나 `page.js` 파일에서 `Metadata` 객체를 export하면 된다.

 

Dynamic Metadata (동적 메타데이터)

// app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: Promise<{ id: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // read route params
  const { id } = await params
 
  // 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) {}
  • 동적인 메타데이터가 필요한 경우에는 `generateMetadata()` 함수를 사용한다.
  • `generateMetadata()` 함수는 서버 컴포넌트에서만 지원된다.

 

메타데이터 제목에 템플릿 사용하기

// app/layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // a default is required when creating a template
  },
}
// app/about/page.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'About',
}
 
// Output: <title>About | Acme</title>
  • 메타데이터 `title`에 일관된 형식을 적용하기 위해 템플릿을 사용할 수 있다.
  • `title.template`을 사용해서 `title`에 접두사 또는 접미사를 추가한다.
  • 템플릿을 사용하면 기본적으로는 `default` 제목이 적용되고, 별도로 메타데이터 `title` 항목을 정의한 페이지에서만 `template` 구조의 제목이 생성된다.

 

실제 구현 사례: 검색 페이지

기본 템플릿 설정

// src/app/layout.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    template: "%s | 웹소설 캘린더",
    default: "웹소설 캘린더",
  },
}
  • 메인 페이지에서는 "웹소설 캘린더"를 보여주고, 다른 페이지에서는 "something | 웹소설 캘린더" 형식으로 제목을 생성한다.

 

동적 메타데이터 구현

import { Metadata } from "next";

type Props = {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

// Dynamic Metadata 생성
export async function generateMetadata({
  searchParams,
}: Props): Promise<Metadata> {
  const { q } = await searchParams;
  const searchQuery = q || "";

  return {
    title: searchQuery ? `${searchQuery} 검색 결과` : "검색",
    description: "관심 있는 작가나 작품을 검색할 수 있어요.",
  };
}
  • 검색 전에는 "검색 | 웹소설 페이지"를, 검색 후에는 "검색어 검색 결과 | 웹소설 캘린더"로 제목을 생성한다.
  • 검색 페이지 URL이 `/search?q=검색어` 형태로 구성되어 있기 때문에 `searchParams`를 받아서 사용했다.

 

generateMetadata 함수의 파라미터와 주의사항

파라미터 종류

  • `generageMetadata()` 함수는 두 개의 파라미터를 받을 수 있다.
  • Path Variable를 사용한다면 `params`를 사용한다.
  • Query Parameter를 사용한다면 `searchParams`를 사용한다.

 

비동기 처리 주의사항

`searchParams` should be awaited before using its properties.
  • `generageMetadata()` 함수는 Dynamic API이기 때문에 `params`나 `searchParams`에 직접 접근할 수 없고, `await`를 사용해 비동기적으로 접근해야 한다.
  • `await`을 사용하지 않으면 위와 같은 에러 메시지가 표시된다.

 

서버 컴포넌트 적용

// src\app\search\page.tsx
export default function Page() {
  return <SearchPage />;
}
  • `generateMetadata()`는 서버 컴포넌트에서만 사용할 수 있는데, 기존의 검색 페이지는 이미 클라이언트 컴포넌트였다.
  • 그래서 `search/page.tsx` 파일 내부의 코드를 따로 `SearchPage`라는 컴포넌트로 분리해서  `search/page.tsx`를 서버 컴포넌트로 만들었다.

 

구현 결과

이제 검색 페이지의 URL이 `/search?q=검색어`일 때, 페이지 제목이 검색어 `검색 결과 | 웹소설 캘린더`로 동적으로 변경된다.

 

 

 

References