frontend
Next.js App Router 메타데이터 완벽 가이드 — generateMetadata로 SEO 최적화하는 방법
Next.js App Router에서는 Head 컴포넌트 대신 Metadata API를 써야 한다. layout.tsx 전역 설정부터 포스트별 generateMetadata, robots.ts와 sitemap.ts까지 정리했다.

- ·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로 조정할 수 있습니다.
관련 글
Next.js 정적 사이트를 Jenkins로 자동 배포하는 방법 — Jenkinsfile로 빌드부터 배포까지
매번 수동으로 빌드하고 서버에 올리던 Next.js 정적 사이트 배포를 Jenkins 파이프라인으로 자동화한 방법을 정리했다. Checkout부터 scp 배포까지 단계별로 설명한다.
Next.js App Router에서 Google Analytics 4 설정하는 방법 — 페이지뷰와 이벤트 추적까지
UA 종료 후 GA4로 전환한 환경에서 Next.js App Router에 Google Analytics 4를 연동하는 방법을 정리했다. next/script 로드, usePathname 페이지뷰 추적, 커스텀 이벤트까지 다룬다.