icon

안동민 개발노트

13장 : SEO 및 메타데이터

Open Graph 태그 활용


웹 페이지의 내용이 소셜 미디어 플랫폼(페이스북, X(트위터), 링크드인 등)에서 공유될 때, 단순히 링크만 표시되는 것보다 풍부한 미리보기(제목, 설명, 이미지)가 함께 표시될 때 사용자의 클릭을 유도할 확률이 훨씬 높아집니다. 이러한 미리보기를 제어하는 표준 프로토콜이 바로 Open Graph (OG) 프로토콜입니다.

Open Graph 태그는 HTML <head> 섹션에 포함되는 메타 태그의 한 종류로, 웹 페이지의 콘텐츠에 대한 구조화된 정보를 제공하여 소셜 미디어 플랫폼이 해당 콘텐츠를 올바르게 이해하고 멋지게 표시할 수 있도록 돕습니다. Next.js App Router에서는 이러한 Open Graph 태그를 손쉽게 설정할 수 있는 강력한 방법을 제공합니다.

이 절에서는 Open Graph 태그의 중요성, 주요 속성, Next.js App Router에서 이를 설정하는 방법, 그리고 효과적인 활용 전략에 대해 상세히 알아보겠습니다.


Open Graph 프로토콜이란?

Open Graph 프로토콜은 페이스북이 웹 페이지를 소셜 그래프의 객체로 통합하기 위해 2010년에 도입한 기술입니다. 이후 많은 소셜 미디어 플랫폼과 메신저 앱(카카오톡, 슬랙 등)이 이 프로토콜을 채택하여 링크 공유 시 사용자에게 일관되고 풍부한 시각적 경험을 제공하고 있습니다.

OG 태그가 제대로 설정되지 않으면, 소셜 미디어 플랫폼은 페이지의 제목, 설명, 이미지를 임의로 추출하여 표시할 수 있으며, 이 경우 미리보기가 어색하거나 정보가 누락되어 링크 클릭률이 낮아질 수 있습니다.


주요 Open Graph 태그 속성

OG 태그는 og: 접두사를 사용하여 정의됩니다. 가장 일반적으로 사용되는 필수 및 선택적 속성은 다음과 같습니다.

  • og:title: (필수) 웹 페이지의 제목입니다. 50~60자 이내로 간결하고 매력적으로 작성하는 것이 좋습니다.
    <meta property="og:title" content="Next.js로 배우는 SEO 가이드" />
  • og:type: (필수) 웹 페이지 콘텐츠의 유형입니다. website (일반 웹사이트), article (블로그 게시물, 기사), book, profile, video.movie 등 다양한 타입이 있습니다.
    <meta property="og:type" content="article" />
  • og:image: (필수) 소셜 미디어 미리보기에서 표시될 이미지의 URL입니다. 이미지는 고품질이어야 하며, 적절한 크기(권장: 1200x630px)와 비율(1.91:1)을 유지하는 것이 좋습니다. 절대 경로(Full URL)를 사용해야 합니다.
    <meta property="og:image" content="https://commerce-lab.dev/images/seo-guide-og.png" />
  • og:url: (필수) 웹 페이지의 표준 URL입니다. 이 URL은 페이지에 접근할 수 있는 정식 주소여야 합니다.
    <meta property="og:url" content="https://commerce-lab.dev/blog/nextjs-seo-guide" />
  • og:description: (선택) 웹 페이지의 간략한 설명입니다. 150~160자 이내로 작성하는 것이 좋으며, 제목과 함께 사용자가 클릭할지 여부를 결정하는 데 중요한 역할을 합니다.
    <meta property="og:description" content="Next.js App Router에서 Open Graph 태그를 활용하여 소셜 미디어 공유를 최적화하는 방법을 알아봅니다." />
  • og:site_name: (선택) 웹사이트의 이름입니다. 제목과 중복되지 않게 웹사이트의 브랜드를 나타냅니다.
    <meta property="og:site_name" content="My Next.js Blog" />
  • og:locale: (선택) 콘텐츠의 언어 및 지역입니다 (예: ko_KR for 한국어, en_US for 미국 영어).
    <meta property="og:locale" content="ko_KR" />
  • og:image:width, og:image:height: (선택) og:image에 지정된 이미지의 너비와 높이(픽셀)입니다. 이 정보를 제공하면 소셜 미디어 플랫폼이 이미지를 미리 로드하고 레이아웃 시프트 없이 올바르게 표시하는 데 도움이 됩니다.
    <meta property="og:image:width" content="1200" />
    <meta property="og:image:height" content="630" />
  • article:published_time, article:author 등 (og:typearticle일 경우): 특정 og:type에 따라 추가적인 메타 속성을 정의할 수 있습니다.

Next.js App Router에서 Open Graph 태그 설정하기

Next.js App Router에서는 13장 1절 정적 메타데이터 설정과 13장 2절 동적 메타데이터 생성에서 다룬 metadata 객체를 사용해 Open Graph 태그를 설정합니다.

metadata.openGraph 객체 안에 필요한 속성을 정의하면, Next.js가 자동으로 HTML <head>에 해당하는 <meta property="og:..." /> 태그를 생성해 줍니다.

정적 Open Graph 태그 설정

애플리케이션 전체의 기본 Open Graph 정보를 루트 레이아웃인 app/layout.tsx에 설정합니다.

app/layout.tsx
import type { Metadata } from 'next';
// ... (기존 임포트 및 폰트 설정) ...

export const metadata: Metadata = {
  // ... (기존 title, description, keywords 등) ...

  // Open Graph 설정
  openGraph: {
    title: '나만의 Next.js 웹사이트',
    description: 'Next.js App Router를 활용하여 구축된 현대적인 웹사이트입니다.',
    url: 'https://commerce-lab.dev', // 웹사이트의 기본 URL
    siteName: 'Next.js 데모 사이트', // 웹사이트 이름
    images: [
      {
        url: 'https://commerce-lab.dev/og-default.png', // 기본 OG 이미지 (공유될 때 표시)
        width: 1200,
        height: 630,
        alt: 'Next.js 데모 사이트 로고',
      },
    ],
    locale: 'ko_KR',
    type: 'website',
  },

  // ... (twitter, robots 등 기타 메타데이터) ...
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ko">
      <body>{children}</body>
    </html>
  );
}

동적 Open Graph 태그 생성

블로그 게시물이나 상품 상세 페이지처럼 URL 파라미터에 따라 내용이 달라지는 경우, generateMetadata 함수를 사용하여 Open Graph 태그를 동적으로 생성합니다. 이 경우, 페이지/레이아웃 레벨의 openGraph 설정이 루트 레이아웃의 설정을 덮어쓰거나 병합합니다.

app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next';

// 가상의 상품 데이터 조회 함수
async function getProduct(id: string) {
  const products = [
    { id: '1', name: '고급 Next.js 신발', description: '편안함과 스타일을 모두 잡은 신발입니다.', price: 120000, imageUrl: 'https://example.com/images/shoe1.jpg' },
    { id: '2', name: 'React 개발자 후드티', description: '개발자를 위한 편안하고 트렌디한 후드티입니다.', price: 55000, imageUrl: 'https://example.com/images/hoodie2.jpg' },
  ];
  return products.find(p => p.id === id);
}

type Props = {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
};

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata // 상위 메타데이터 (루트 layout.tsx 등)
): Promise<Metadata> {
  const product = await getProduct(params.id);

  if (!product) {
    return { title: '상품을 찾을 수 없습니다.' };
  }

  // 상위 openGraph 이미지를 가져와 현재 이미지와 병합
  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: product.name,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      url: `https://commerce-lab.dev/products/${product.id}`,
      siteName: 'Next.js 쇼핑몰', // 웹사이트 이름 (필요에 따라 덮어쓸 수 있음)
      images: [
        {
          url: product.imageUrl,
          width: 800,
          height: 600,
          alt: product.name,
        },
        ...previousImages, // 기본 OG 이미지도 함께 포함 (선택 사항)
      ],
      type: 'product', // 상품 페이지는 'product' 타입이 적합
    },
    // Twitter Card도 비슷하게 설정 가능
    twitter: {
      card: 'summary_large_image',
      title: product.name,
      description: product.description,
      images: [product.imageUrl],
    }
  };
}

export default async function ProductDetailPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);

  if (!product) {
    return <div>상품을 찾을 수 없습니다.</div>;
  }

  return (
    <div style={{ padding: '20px', maxWidth: '800px', margin: '20px auto', border: '1px solid #FF5722', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)' }}>
      <h1 style={{ color: '#FF5722', textAlign: 'center', marginBottom: '20px' }}>{product.name}</h1>
      <p style={{ textAlign: 'center', fontSize: '1.2em', color: '#666' }}>가격: {product.price.toLocaleString()}</p>
      {product.imageUrl && (
        <img src={product.imageUrl} alt={product.name} style={{ maxWidth: '100%', height: 'auto', display: 'block', margin: '20px auto' }} />
      )}
      <p style={{ lineHeight: 1.6, fontSize: '1.1em', color: '#333' }}>{product.description}</p>
    </div>
  );
}

효과적인 Open Graph 태그 활용 전략

  • 고품질 이미지 사용: og:image는 소셜 미디어 미리보기의 핵심입니다. 고해상도(최소 1200x630px), 시각적으로 매력적이며, 페이지 내용을 잘 나타내는 이미지를 사용해야 합니다.
  • 텍스트 오버레이 피하기: 이미지 위에 중요한 텍스트를 직접 오버레이하는 것은 피하는 것이 좋습니다. 소셜 미디어 플랫폼은 이미지를 축소하거나 잘라낼 수 있기 때문입니다.
  • 절대 URL 사용: og:urlog:image 속성에는 반드시 https://로 시작하는 절대 URL을 사용해야 합니다. 상대 경로는 인식되지 않습니다.
  • 캐시 문제 해결: 소셜 미디어 플랫폼은 OG 태그를 캐싱하는 경향이 있습니다. 태그를 수정한 후에는 해당 플랫폼의 디버깅 도구를 사용하여 캐시를 새로고침해야 변경 사항이 적용됩니다.
  • 다양한 og:type 활용: 페이지의 성격에 맞는 og:type을 설정하여 검색 엔진과 소셜 미디어 플랫폼에 더 정확한 정보를 제공합니다. 예를 들어, 동영상 페이지에는 video.movie, 레시피 페이지에는 article 또는 website 타입을 사용하고 추가적인 article: 속성을 활용할 수 있습니다.
  • Twitter Card와 함께 사용: 트위터는 자체 twitter: 메타 태그를 사용하지만, og: 태그를 폴백(fallback)으로 활용합니다. 따라서 og: 태그를 잘 설정해두면 트위터에서도 기본적인 미리보기가 가능합니다. 다만 트위터 고유의 카드 타입을 활용하려면 twitter: 태그를 별도로 설정하는 것이 좋습니다 (예: summary_large_image).
  • og:locale 설정: 다국어 사이트의 경우 og:locale을 사용하여 콘텐츠의 기본 언어를 명시하고, og:locale:alternate를 사용하여 다른 언어 버전을 지정할 수 있습니다.

Open Graph 태그는 웹 페이지의 소셜 미디어 가시성과 매력을 극대화하는 강력한 도구입니다. Next.js App Router의 내장된 메타데이터 기능을 활용하면 이러한 태그들을 쉽게 관리하고, 사용자에게 최적화된 공유 경험을 제공할 수 있습니다.

목차