frontend

Next.js App Router 메타데이터 완벽 가이드 — generateMetadata로 SEO 최적화하는 방법

Next.js App Router에서는 Head 컴포넌트 대신 Metadata API를 써야 한다. layout.tsx 전역 설정부터 포스트별 generateMetadata, robots.ts와 sitemap.ts까지 정리했다.

Next.jsApp RouterSEO메타데이터generateMetadata
Next.js App Router의 generateMetadata 함수 코드 예시 — slug 파라미터로 포스트별 동적 메타데이터를 생성하는 구조
  • ·Next.js App Router Metadata API: 13.2 버전부터 도입
  • ·generateMetadata는 서버에서 실행되며 async/await 사용 가능
  • ·layout.tsx의 title template: '%s | 사이트명' 형태로 하위 페이지 타이틀 자동 조합
  • ·app/robots.ts와 app/sitemap.ts: 코드로 robots.txt와 sitemap.xml을 자동 생성
Pages Router 기반 프로젝트를 App Router로 마이그레이션하면서 메타데이터 설정 방식이 완전히 달라졌다는 걸 처음에 몰랐다. Head 컴포넌트를 쓰던 습관으로 app/layout.tsx에 next/head를 import했다가 서버 컴포넌트에서 경고가 뜨고 나서야 Metadata API로 전환했다. generateMetadata를 쓰기 시작하면서 블로그 포스트 페이지마다 타이틀과 Open Graph 이미지가 자동으로 달라지는 걸 확인했고, 이후로는 Pages Router 방식으로 돌아가고 싶다는 생각이 없어졌다.

Next.js App Router 메타데이터 API 이해하기

Next.js App Router에서 메타데이터 설정 방식이 바뀐 이유

Next.js Pages Router에서는 메타데이터를 Head 컴포넌트로 설정했다. 각 페이지 파일에서 next/head를 import하고, Head 안에 title, meta 태그를 직접 작성하는 방식이었다. App Router로 넘어오면서 이 방식이 완전히 달라졌다. App Router는 서버 컴포넌트 기반으로 동작하고, next/head는 클라이언트 컴포넌트 전용이라 서버 컴포넌트에서 사용하면 경고가 뜨거나 무시된다. 대신 Next.js 13.2부터 Metadata API가 도입됐다. layout.tsx나 page.tsx에서 metadata 객체를 export하거나 generateMetadata 함수를 export하면 Next.js가 자동으로 HTML head에 메타태그를 생성해준다. 이 방식의 장점은 타입 안전성이다. Metadata 타입을 import해서 사용하면 오타나 잘못된 필드 이름을 컴파일 타임에 잡을 수 있다. Pages Router의 Head 컴포넌트는 문자열을 직접 작성하기 때문에 오타가 있어도 런타임에서야 알 수 있었다. App Router Metadata API는 이 과정을 더 구조적으로 만들었고, Open Graph와 Twitter Card 설정도 객체 형태로 명확하게 선언할 수 있다.

Next.js App Router 메타데이터 정적 선언과 generateMetadata의 차이

App Router에서 메타데이터를 설정하는 방법은 두 가지다. 첫 번째는 정적 metadata 객체를 export하는 방식이다. layout.tsx나 page.tsx에서 export const metadata: Metadata = { title: '...', description: '...' } 형태로 선언한다. 페이지의 메타데이터가 고정된 경우, 예를 들어 홈페이지나 소개 페이지처럼 내용이 바뀌지 않는 페이지에 적합하다. 두 번째는 generateMetadata 함수를 export하는 방식이다. 이 함수는 페이지의 params와 searchParams를 인수로 받기 때문에 URL 파라미터에 따라 다른 메타데이터를 동적으로 생성할 수 있다. 블로그 포스트 페이지처럼 URL의 slug에 따라 타이틀과 설명이 달라져야 하는 경우에 쓴다. generateMetadata 함수 안에서 데이터를 fetch해서 포스트 제목을 가져오고, 그것을 title로 설정하는 패턴이 일반적이다. 이 함수는 서버에서 실행되기 때문에 데이터베이스 쿼리나 fetch 호출을 직접 할 수 있다. 같은 페이지 컴포넌트에서 데이터를 fetch하는 경우와 중복이 생길 수 있는데, Next.js의 fetch 캐싱 덕분에 동일한 요청은 한 번만 실행된다.

Next.js App Router 기본 메타데이터 설정하기

Next.js App Router layout.tsx에서 메타데이터를 설정하는 방법

App Router 프로젝트에서 전역 메타데이터는 app/layout.tsx에서 설정한다. Metadata 타입을 next 패키지에서 import하고 metadata 객체를 export하면 된다. title 필드는 문자열로 지정하거나, template을 설정해서 하위 페이지에서 자동으로 사이트 이름이 붙도록 구성할 수 있다. title: { default: '사이트명', template: '%s | 사이트명' } 형태로 설정하면, 하위 page.tsx에서 metadata = { title: '포스트 제목' }처럼 title만 설정해도 자동으로 '포스트 제목 | 사이트명'이 만들어진다. description은 검색 결과에 표시되는 설명 텍스트로 160자 이내로 핵심 내용을 담는 게 좋다. robots 필드는 검색 엔진 색인 여부를 제어한다. 기본값은 모든 페이지가 색인되는 상태이고, 특정 페이지만 색인을 막으려면 그 페이지의 page.tsx에서 별도로 robots 설정을 선언하면 된다. layout.tsx에서 한 번만 설정해두면 모든 하위 페이지에 자동으로 적용되기 때문에 반복 작성 없이 일관된 메타데이터 구조를 유지할 수 있다.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: '사이트명',
    template: '%s | 사이트명',
  },
  description: '사이트 설명을 160자 이내로 작성',
  robots: { index: true, follow: true },
  openGraph: {
    type: 'website',
    siteName: '사이트명',
  },
}

Next.js App Router에서 Open Graph 메타데이터를 설정하는 방법

Open Graph 메타데이터는 링크를 SNS나 메신저에 공유했을 때 미리보기로 표시되는 제목, 설명, 이미지를 제어한다. Metadata 객체의 openGraph 필드에 설정한다. type, title, description, images 필드가 주요 항목이다. images는 배열로 여러 이미지를 지정할 수 있고, 각 이미지에 url, width, height, alt를 설정한다. 권장 이미지 크기는 1200x630이다. App Router에서는 opengraph-image.tsx 파일을 만들어서 동적으로 OG 이미지를 생성하는 기능도 제공한다. 이 파일을 app/ 디렉토리나 특정 라우트 폴더에 두면 Next.js가 자동으로 해당 경로의 OG 이미지 엔드포인트를 만들어준다. 내부적으로 @vercel/og를 사용해서 React 컴포넌트를 이미지로 변환한다. OG 이미지를 제대로 설정했을 때와 그렇지 않을 때 카카오톡이나 슬랙에 링크를 붙였을 때의 반응 차이가 체감상 확실히 났다. openGraph 설정은 소홀하기 쉬운 부분이지만 공유 시 첫인상을 결정하는 요소라서 반드시 챙겨두는 게 좋다.

Next.js App Router 동적 메타데이터와 SEO 심화

Next.js App Router generateMetadata로 포스트별 동적 메타데이터를 생성하는 방법

블로그나 문서 사이트처럼 URL에 따라 내용이 달라지는 페이지는 generateMetadata 함수를 사용한다. app/posts/[slug]/page.tsx에서 함수를 export하면 된다. 함수는 params와 searchParams를 인수로 받는다. params.slug로 현재 포스트의 슬러그를 가져와서 데이터를 fetch하고, 그 결과를 바탕으로 Metadata 객체를 반환한다. 같은 컴포넌트에서 포스트 데이터를 fetch할 때 URL이 동일하다면 Next.js의 fetch 캐싱이 중복 요청을 자동으로 제거해줘서 성능 손실이 없다. 포스트 썸네일 이미지를 OG 이미지로 설정하면 SNS 공유 시 포스트마다 다른 이미지가 표시된다. notFound()를 generateMetadata에서 호출할 수도 있어서 존재하지 않는 슬러그에 대한 404 처리도 이 함수 안에서 가능하다. 처음에 이 방식을 쓰기 전에는 모든 포스트 페이지가 동일한 타이틀과 설명을 갖고 있었는데, generateMetadata 적용 후 Search Console에서 각 포스트 URL이 올바른 메타데이터로 색인되는 걸 확인했다.

import type { Metadata } from 'next'

export async function generateMetadata(
  { params }: { params: { slug: string } }
): Promise<Metadata> {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [{ url: post.thumbnail, width: 1200, height: 630 }],
    },
  }
}

Next.js App Router robots.ts와 sitemap.ts로 SEO를 최적화하는 방법

App Router에서 robots.txt와 sitemap.xml을 코드로 생성하는 방법을 제공한다. app/robots.ts 파일을 만들고 MetadataRoute.Robots 타입의 객체를 반환하는 함수를 export하면 Next.js가 자동으로 /robots.txt 경로를 만들어준다. sitemap은 app/sitemap.ts에서 MetadataRoute.Sitemap 타입 배열을 반환하면 된다. 포스트 목록을 fetch해서 각 URL을 배열로 만들어 반환하면 동적으로 sitemap이 생성된다. 정적 사이트 빌드(output: 'export')를 사용하는 경우에는 robots.ts와 sitemap.ts가 빌드 시 정적 파일로 변환된다. Google Search Console에 sitemap URL을 등록하면 구글이 사이트 구조를 빠르게 파악할 수 있다. 직접 확인했을 때 sitemap을 등록한 뒤 새로 발행한 포스트가 Search Console의 URL 검사에서 인식되는 시간이 눈에 띄게 줄었다. robots.ts에서 disallow를 설정하면 검색 엔진이 특정 경로를 크롤링하지 못하도록 막을 수 있다. 관리자 경로나 미완성 페이지를 검색 결과에서 제외할 때 유용하다. 두 파일 모두 코드로 관리되기 때문에 배포 시 자동으로 최신 상태가 유지된다.

자주 묻는 질문

Pages Router에서 App Router로 마이그레이션할 때 Head 컴포넌트를 그냥 쓸 수 없나요?+

app/ 디렉토리의 서버 컴포넌트에서는 next/head의 Head 컴포넌트를 쓸 수 없습니다. App Router에서는 Metadata 객체나 generateMetadata 함수를 export하는 방식을 사용해야 합니다. 클라이언트 컴포넌트('use client')에서도 Head는 App Router에서 지원되지 않습니다.

generateMetadata에서 fetch한 데이터를 page 컴포넌트에서도 써야 하면 두 번 요청이 가나요?+

아닙니다. Next.js의 fetch는 같은 URL, 같은 옵션이면 자동으로 캐싱됩니다. generateMetadata와 page 컴포넌트에서 동일한 fetch를 호출해도 실제 네트워크 요청은 한 번만 일어납니다.

layout.tsx에서 설정한 메타데이터를 하위 page.tsx에서 덮어쓸 수 있나요?+

네, 가능합니다. 하위 page.tsx에서 같은 필드를 설정하면 layout.tsx의 설정을 덮어씁니다. title template을 layout.tsx에 설정해두면 page.tsx에서 title만 선언해도 템플릿이 자동으로 적용됩니다.

opengraph-image.tsx를 쓰면 OG 이미지가 자동으로 만들어지나요?+

네. app/ 폴더 또는 특정 라우트 폴더에 opengraph-image.tsx 파일을 만들고 React 컴포넌트를 export하면 Next.js가 해당 경로의 OG 이미지 엔드포인트를 자동으로 생성합니다. 기본 크기는 1200x630이고 size export로 조정할 수 있습니다.

관련 글