IT·개발· 10분 읽기

Next.js 15 메타태그 완벽 가이드 — og:image부터 hreflang까지

Next.js 15는 App Router 기반의 Metadata API를 통해 정적·동적 메타태그를 선언적으로 관리할 수 있다. generateMetadata() 함수로 페이지별 동적 og:image, title, description을 자동 생성할 수 있다.

광고

핵심 요약

>

- Next.js 15는 App Router 기반의 Metadata API를 통해 정적·동적 메타태그를 선언적으로 관리할 수 있다. - generateMetadata() 함수로 페이지별 동적 og:image, title, description을 자동 생성할 수 있다. - Twitter Card, hreflang, canonical URL, 구조화 데이터(JSON-LD)까지 단일 파일에서 제어 가능하다. - 잘못 설정된 메타태그는 소셜 미리보기 오류와 검색 순위 하락으로 직결되므로, 메타태그 검사 도구로 배포 전 반드시 점검하자.


Next.js 15 Metadata API란 무엇인가?

Next.js 13부터 도입된 App Router는 기존 컴포넌트 방식을 대체하는 Metadata API를 제공한다. Next.js 15에서는 이 API가 더욱 정교해져, layout.tsxpage.tsx 파일에서 metadata 객체를 내보내거나 generateMetadata() 함수를 사용하는 두 가지 방식으로 메타태그를 제어할 수 있다.

정적 메타데이터

가장 간단한 형태는 metadata 객체를 직접 내보내는 방식이다.

// app/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Next.js 15 메타태그 가이드',
  description: 'og:image부터 hreflang까지, Next.js 메타태그의 모든 것',
}

이 방식은 콘텐츠가 빌드 시점에 확정되는 정적 페이지에 적합하다. 블로그 홈이나 소개 페이지처럼 내용이 자주 바뀌지 않는 경우에 사용한다.

동적 메타데이터: generateMetadata()

블로그 포스트나 상품 상세 페이지처럼 URL 파라미터에 따라 내용이 달라지는 경우에는 generateMetadata() 함수를 사용한다.

// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { slug: string }
}

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const post = await fetchPost(params.slug)

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.ogImage, width: 1200, height: 630 }],
    },
  }
}

generateMetadata()는 서버에서 실행되므로 데이터베이스 조회나 외부 API 호출이 가능하다. 반환값은 Metadata 타입이며, Next.js가 자동으로 태그에 주입한다.


og:image를 제대로 설정하는 방법은 무엇인가?

Open Graph 이미지는 카카오톡, 슬랙, 트위터 등 소셜 플랫폼에서 링크를 공유할 때 나타나는 미리보기 이미지다. 잘못 설정하면 빈 이미지나 깨진 레이아웃이 노출되어 클릭률이 크게 떨어진다.

기본 og:image 설정

export const metadata: Metadata = {
  openGraph: {
    title: '페이지 제목',
    description: '페이지 설명',
    url: 'https://example.com/blog/my-post',
    siteName: '사이트 이름',
    images: [
      {
        url: 'https://example.com/og/my-post.png',
        width: 1200,
        height: 630,
        alt: '게시글 대표 이미지',
      },
    ],
    locale: 'ko_KR',
    type: 'article',
  },
}

Next.js의 동적 og:image 생성 — ImageResponse

Next.js 15는 next/ogImageResponse를 활용해 서버에서 OG 이미지를 동적으로 렌더링하는 기능을 지원한다. 별도의 이미지 편집 없이 코드로 디자인을 제어할 수 있다.

// app/og/route.tsx
import { ImageResponse } from 'next/og'

export const runtime = 'edge'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const title = searchParams.get('title') ?? '기본 제목'

  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          fontSize: 60,
          background: '#0f172a',
          color: 'white',
          width: '100%',
          height: '100%',
          alignItems: 'center',
          justifyContent: 'center',
          padding: '40px',
        }}
      >
        {title}
      </div>
    ),
    { width: 1200, height: 630 }
  )
}

이후 generateMetadata()에서 해당 엔드포인트를 참조하면 된다.

images: [{ url: `https://example.com/og?title=${encodeURIComponent(post.title)}` }]

Twitter Card 설정

Twitter(현 X)는 자체 메타태그 규격을 사용하지만, Next.js Metadata API는 이를 twitter 키로 통합 관리한다.

export const metadata: Metadata = {
  twitter: {
    card: 'summary_large_image',
    title: '페이지 제목',
    description: '페이지 설명',
    creator: '@handle',
    images: ['https://example.com/og/my-post.png'],
  },
}

card 값은 summary, summary_large_image, app, player 중 선택한다. 블로그 포스트에는 summary_large_image가 가장 적합하다.


hreflang으로 다국어 SEO를 완성하는 방법

hreflang 태그는 동일한 콘텐츠의 언어·지역별 버전을 검색 엔진에 알려주는 신호다. 한국어와 영어 버전을 함께 운영하는 사이트라면 반드시 설정해야 한다. 설정이 누락되면 구글이 언어별 페이지를 중복 콘텐츠로 판단해 검색 노출에 불이익을 줄 수 있다.

alternates로 hreflang 설정

export const metadata: Metadata = {
  alternates: {
    canonical: 'https://example.com/ko/blog/my-post',
    languages: {
      'ko-KR': 'https://example.com/ko/blog/my-post',
      'en-US': 'https://example.com/en/blog/my-post',
      'ja-JP': 'https://example.com/ja/blog/my-post',
    },
  },
}

Next.js는 이 설정을 기반으로 아래와 같은 태그를 자동 생성한다.

<link rel="canonical" href="https://example.com/ko/blog/my-post" />
<link rel="alternate" hreflang="ko-KR" href="https://example.com/ko/blog/my-post" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en/blog/my-post" />
<link rel="alternate" hreflang="ja-JP" href="https://example.com/ja/blog/my-post" />

canonical URL 설정의 중요성

canonical URL은 동일하거나 유사한 콘텐츠가 여러 URL에 존재할 때 검색 엔진에게 "이 URL이 원본"임을 알리는 태그다. 페이지네이션, UTM 파라미터, www 유무 차이 등으로 중복 URL이 발생하는 경우 canonical 설정이 없으면 검색 점수가 분산된다.

alternates: {
  canonical: 'https://example.com/blog/my-post',
}

절대 경로를 사용하고, HTTPS와 www 여부를 사이트 전체에 걸쳐 일관되게 유지해야 한다.


구조화 데이터(JSON-LD)로 리치 결과 획득하기

구조화 데이터는 검색 결과에 별점, 작성자, 게시일 등의 추가 정보를 표시하는 리치 결과를 가능하게 한다. Next.js에서는