Next.js에서 동적으로 메타데이터와 오픈그래프 적용하기

Doeunnkimm·2024년 2월 16일
21

FE log

목록 보기
5/6
post-thumbnail

최근 프로젝트에서는 본인의 고유 id를 통해 url에 접근할 수 있고, 그 안에 내용들은 id마다 서로 다른 정보를 가지고 있어요. 그래서 동적으로 메타데이터와 오픈그래프를 적용하기로 했고, 최종적으로 적용 결과는 아래와 같았습니다.

구글 검색 결과에서 최상위에 위치하고 있어요.

title에도 id가 들어가 있고, opengraph-image에도 제 id가 들어가도록 해주었어요. 이외에도 <head> 태그를 뜯어보면 동적으로 설정되도록 적용했습니다.

🤔 먼저 알아보자

Metadata

<meta> 태그는 웹 사이트에 대한 정보를 제공하는 기능을 하고 있습니다. 그러한 정보를 메타데이터 라고 부릅니다. 메타데이터는 웹 페이지에 나타나지 않고, 검색 엔진이나 웹 크롤러를 통해 수집됩니다.

Metadata는 웹사이트에서 <head> 태그를 열어보면 쉽게 볼 수가 있는데요!

메타태그의 종류에는 대표적으로 keywords, description, title 등이 있습니다. 이외에도 브라우저 호환성을 지정하거나, 제작 도구, 저작권, 최종 수정일, 뷰포트 정의 등 다양한 정보들을 제공할 수 있습니다.

한 마디로 정의하면, 웹 사이트에 대한 정보

왜 필요한 걸까?

검색 엔진 최적화 (SEO)를 위해서

적절히 구현된 메타데이터는 검색 엔진인 웹 페이지를 정확하게 색인화하고 순위를 지정하여, 궁긍적으로 검색 결과에서 상위에 노출되도록 할 수 있게 해줍니다.

즉, 웹 사이트의 정보가 사용자에게 잘 전달되기 위해서는 검색엔진에서 적절히 인식되고 노출되어야 하고, 따라서 웹사이트는 메타데이터를 사용해야 합니다. 이는 곧 사용자가 우리 서비스를 쉽게 찾아볼 수 있도록 하는 것을 의미하므로 인지도를 높이고, 사용자 유입을 증가시키는 데 중요한 역할을 하는 것이죠!

좋은 메타데이터란 뭘까

🤔 메타데이터를 그냥 막 설정한다고 해서 검색 결과가 좋아지는 건 아닐 겁니다. 그렇다면 좋은 메타데이터란 무엇일까요?

웹 페이지의 내용을 정확하게 반영하고, 사용자가 검색할 만한 키워드를 포함하는 것

(당연,,🥹)

다양한 레퍼런스들을 보면 공통적으로 사용자가 클릭할 만한, 너무 길지 않은을 많이 언급하는 거 같아요. 결론적으로 정답이 있다기 보다, 정확하고 사용자에게 이목을 끌만한키워드가 포함되면 될 것 같다고 생각 들었어요.

저희 웹 사이트의 경우 구글 검색 시 최상단에 노출되게 되었어요.

물론 저희 팀에서도 사용자가 검색할 만한 키워드 포함을 신경 쓰면서 메타데이터를 추가했지만, 그것보다도 유니크한 서비스명 부분에서 이점을 얻어갔다 생각합니다.

사실 경쟁할 대상이 없기 때문에 최상위에 떴다고 볼 수도 있겠지만,, 아무튼

새로 시작하는 웹 서비스의 경우 너무 흔한 키워드거나 이미 너무 유명한 컨텐츠가 있는 경우 불리하게 시작할 수밖에 없는 것 같아요.

저희는 덕분에 등록되자마자 서비스 홍보할 때 "반디부디 검색해서 들어오면 됨 ㅇㅇ" 라고 할 수 있었습니다 👍

포털 사이트에 내 사이트 노출하기

포털 사이트에서 웹 사이트를 노출시키려면, 대부분의 경우 해당 포털 사이트의 웹마스터 도구나 검색 엔진 등록 서비스를 이용해 서비스를 등록해야 해요.

사이트를 직접 등록하면 포털 사이트가 사이트를 더 빠르게 인식하고, 사이트의 색인화 상태나 이슈 등을 효과적으로 관리할 수 있습니다.

네이버, 구글, 다음, 야후 등록 방법 및 SEO 내용이 담긴 가이드

사이트를 등록하지 않은 경우, 검색 엔진의 크롤러가 자동으로 새로운 사이트를 발견하고 색인화할 수도 있겠지만.. 그들이 와주기 전에.. 필요한 우리가 먼저 등록해서 더 빠르게 인식하도록 하는 게 좋겠습니다,. 😮‍💨

Open Graph

위와 같이 미리보기 화면을 구성할 수 있게 해주는 메타데이터를 오픈 그래프라고 해요.

웹 사이트 URL을 보내는 순간, 크롤러가 잽싸게 해당 URL을 먼저 방문해서 웹 사이트에 있는 오픈 그래프 데이터를 수집해 미리보기 형식으로 구성해 주는 방식입니다.

왜 필요한 걸까?

정보를 공유할 때 URL로만 구성된 텍스트보다 잘 정돈된 이미지, 제목, 내용으로 구성된 미리보기가 클릭률이 높기 때문이죠!

왼쪽과 오른쪽 중 어딜 더 누르고 싶으신가요 🤔

트위터는 왜 따로 또..

오픈 그래프는 Facebook에 의해 만들어진 프로토콜인데요! 다른 소셜 미디어 플랫폼들도 이를 지원합니다.

그런데, 트위터는 자체적인 메타데이터 태그를 가지고 있어요. 이를 Twitter Cards라고 부르고, 트위터에서 웹페이지를 공유할 때 어떻게 표시될지를 정의합니다.

그래서 오픈 그래프를 정의할 때 twitter에 대해서는 따로 정의해 주어야 트위터에서 최적으로 표시되는 데 도움이 됩니다.

🌪️ 동적으로 적용?

위에서 Metadata는 웹 사이트에 대한 정보, Open Graph는 미리보기 제공이라고 알아보았는데요! 이를 동적으로 적용하는 것에 대해 알아보려고 해요.

즉, 페이지 별로 콘텐츠에 따라 메타데이터를 동적으로 변경하는 것을 말합니다. 이는 각 페이지가 각각의 메타데이터를 가지고, 이를 통해 검색 엔진이 각 페이지의 콘텐츠를 정확하게 이해할 수 있도록 돕습니다.

또, 오픈그래프에 대해 동적으로 적용한다면, 페이지 별로 다른 미리보기를 보여줄 수 있어요.

대표적으로 블로그를 예시로 들 수 있어요.

velog에서도 페이지 별로 서로 다른 미리보기를 가지고 있는 것처럼 말이죠!

🤩 동적으로 적용하기 좋은 상황

  • 개별 페이지의 내용이 중요한 경우
    블로그 포스트, 뉴스 기사, 제품 페이지 등 개별 페이지의 내용이 중요하고, 각 페이지가 고유한 정보를 가지고 있는 경우
  • 사용자 정의 콘텐츠
    사용자가 생성하는 콘텐츠가 있는 경우
  • 웹 사이트의 내용이 자주 변경되는 경우
    웹 사이트의 내용이 시간에 따라 자주 변경되는 경우, 동적으로 반영하면 최신 상태를 정확하게 반영 가능
  • 검색 엔진 최적화(SEO)
    동적 메타데이터는 검색 엔진이 각 웹 페이지를 정확하게 이해하는 데 도움

위와 같은 상황에서 동적 메타데이터를 적용하여 웹 사이트의 효과를 높일 수 있습니다.

🫡 이제 진짜 해보자

제가 프로젝트에서 동적으로 MetadataOpenGraph를 어떻게 적용했는지를 정리해보려고 합니다.

  • with Next.js (v14)

🖤 Next.js 감사하다..

친절한 Next.js의 공식 문서 덕분에 코드 자체를 작성하는 것에는 큰 어려움은 없었어요. 위 레퍼런스를 기준으로 저희 팀에서 적용한 방식을 이야기 해볼게요!

1. 메타데이터 정보는 상수로

이 부분이 나중에 갈수록 아 잘했었다 라는 생각이 들었었는데요. 메타데이터 정보를 입력하다보면 같은 내용을 여러 군데에서 작성할 일이 많아서 상수화 해두었더니 간편하게 사용하고 수정할 때도 간편해서 좋았어요. 추천 !

// @/constants/metadata.ts
export const META = {
  title: '반디부디: 내가 직접 그리는 나의 인생지도',
  siteName: 'BANDIBOODI | 반디부디',
  description:
    '이루고 싶은 목표를 지도에 남겨보세요. 목표와 관련된 다양한 스티커를 붙여 나만의 지도를 만들고 공유할 수 있어요.',
  keyword: [
    '반디부디',
    'bandiboodi',
    '인생지도',
    '신년계획',
    '계획',
    '목표설정',
    '목표달성',
    '자기계발',
    '회고',
    '응원',
  ],
  url: 'https://www.bandiboodi.com',
  googleVerification: 'xxx',
  naverVerification: 'xxx',
  ogImage: '/opengraph-image.png',
} as const;

googleVerificationnaverVerification에는 사이트를 등록하고 받은 검증 코드를 입력해주면 됩니다.

위 코드에서 보이는 ogImage 경로는 참고로 public 폴더 기준 경로이며, 이 이미지는 정적 opengraph-image로 사용할 이미지 입니다.

2. metadata를 만드는 함수

원래는 저희 팀에서도 정적 메타데이터만을 사용할 때는 layout.tsx 파일에 바로 넣어서 사용했었는데요. 이제는 동적으로 인자를 받아서 metadata를 생성할 수 있도록 함수를 정의해보려고 합니다.

export const getMetadata = (metadataProps?: generateMetadataProps) => {
  const { title, description, asPath, ogImage } = metadataProps || {};

  const TITLE = title ? `${title} | 반디부디` : META.title;
  const DESCRIPTION = description || META.description;
  const PAGE_URL = asPath ? asPath : '';
  const OG_IMAGE = ogImage || META.ogImage;

  const metadata: Metadata = {
    metadataBase: new URL(META.url),
    alternates: {
      canonical: PAGE_URL,
    },
    title: TITLE,
    description: DESCRIPTION,
    keywords: [...META.keyword],
    openGraph: {
      title: TITLE,
      description: DESCRIPTION,
      siteName: TITLE,
      locale: 'ko_KR',
      type: 'website',
      url: PAGE_URL,
      images: {
        url: OG_IMAGE,
      },
    },
    verification: {
      google: META.googleVerification,
      other: {
        'naver-site-verification': META.naverVerification,
      },
    },
    twitter: {
      title: TITLE,
      description: DESCRIPTION,
      images: {
        url: OG_IMAGE,
      },
    },
  };

  return metadata;
};

우선 인자 자체를 optional로 하여 아무 것도 넘기지 않고 호출했을 때는 default로 해둔 값들을 metadata로 가지도록 했어요. 또, 값도 필요한 것들만 넘기도록 하여 넘기지 않은 인자에 대해서는 default값을 가지도록 했습니다.

인자로 받은 값이 있다면, 그걸 통해 metadata를 생성합니다. 사용할 때는 아래와 같이 사용할 수 있어요.

🤔 metadataBase

상대 경로를 사용할 수 있게 해주는 필드입니다. 즉 해당 필드를 설정해두면, 다른 필드들에 /example로 입력하면 실제 <head>에는 (metadataBase로 설정한 경로)/example 이렇게 들어가게 되는 것이죠!

Next.js에서 Metadata 타입의 metadataBase에는 URL 객체 타입이 들어가야 하므로 new URL() 메서드를 사용해줘야 합니다.

친절한 공식문서

🤔 alternates.canonical

웹페이지가 다른 URL에서 동일하게 접근 가능할 때, 어떤 버전이 원본(주요) 콘텐츠임을 검색 엔진에 알려주는 요소입니다.

예를 들어, 온라인 쇼핑몰에서 같은 상품을 여러 카테고리에서 보여주는 경우가 있을 수 있는데요.

  • www.example.com/clothes/shirt/123 (1)
  • www.example.com/sale/shirt/123 (2)

이 두 URL이 같은 상품 페이지를 가리키고 있지만, 검색 엔진은 이 두 페이지를 서로 다른 콘텐츠로 인식할 수 있습니다. 이렇게 되면, 중복 콘텐츠 문제가 발생하고, 각 페이지가 독립적으로 SEO 점수를 쌓게 됩니다.

이런 경우, Canonical Tag를 사용하여 어떤 URL이 원본 URL인지를 명확히 지정할 수 있습니다. 예를 들어, www.example.com/clothes/shirt/123 (2)를 원본 URL로 지정한다면, 검색 엔진은 www.example.com/sale/shirt/123 (1) 페이지의 콘텐츠가 원래 www.example.com/clothes/shirt/123 (2)에서 생성된 것임을 인식하고, 이를 원본 URL로 취급하게 됩니다. 이로써 중복 콘텐츠 문제를 해결하고, SEO 점수를 하나의 페이지에 집중시킬 수 있는 것이죠!

저희 팀 서비스는 당장은 그런 페이지는 존재하지 않았어요. 그래도 이 태그를 통해 검색 엔진에게 원본 컨텐츠임을 명확하게 알려줄 수 있는 역할로 우선 사용하되, 추후에 URL 변동과 같은 경우를 대비하기 위해 우선 추가하기로 했어요.

3. Metadata 적용

Next.js에서는 fetch된 Data 혹은 slug값을 기반으로 메타데이터를 동적으로 생성할 수 있도록 generateMetadata라는 함수를 제공합니다.

저희 팀에서도 slug값에 포함되어 있는 id를 사용하기 위해 generateMetadata 함수를 사용했습니다.

export const generateMetadata = async ({ params: { username } }: HomeRouteParams): Promise<Metadata> => {
  return getMetadata({ title: `반짝반짝 빛날 ${username}님의 인생지도`, asPath: `/home/${username}` });
};

generateMetadata 함수의 리턴값으로는 미리 선언했던 인자를 통해 메타데이터 객체를 생성해주는 함수를 호출하여 리턴해주었습니다.

고민했던 부분

asPath 프로퍼티의 경우, 당장은 현재 url을 그대로 넘기면 되는 부분이라, 메타데이터를 주입하는 기능의 컴포넌트를 선언하여 usePathname 과 같은 hook을 사용해서 지금의 url을 읽어 내부에서 바로 처리할 수도 있었습니다.

그러할 경우, Metadata 타입을 이용한 객체를 사용하는 대신 <Head> 컴포넌트를 사용하면서(둘 다 Next.js에서 제공하는 기능이긴 합니다) 아래와 같이 적용해 주어야 한다는 점이 조금 번거롭게 다가왔던 것 같습니다.

<Head>
  <title>{TITLE}</title>
  <link rel="canonical" href={URL} />
  <meta name="description" content={DESCRIPTION} />

  <meta property="og:title" content={TITLE} />
  <meta property="og:description" content={DESCRIPTION} />
  <meta property="og:image" content={IMAGE} />
  <meta property="og:url" content={URL} />

  {/* for twitter */}
  <meta name="twitter:title" content={TITLE} />
  <meta name="twitter:description" content={DESCRIPTION} />
  <meta name="twitter:image" content={IMAGE} />
</Head>

Metadata 타입을 붙여서 사용할 경우, 올바르지 않은 프로퍼티에 대해서는 컴파일 에러로 확인할 수 있다는 점에서도 편리하게 느껴졌었거든요!

그래서 고민 끝에, 필요한 경우 경로를 asPath를 통해 넘기는 방식으로 결정했습니다.

4. 동적 opengraph-image 적용

저희 팀이 원하는 opengraph-image 설정은 다음과 같습니다.

  • 동적으로 설정하지 않았을 경우에는 정적 이미지로 설정해둔 경로가 잡히도록
  • 동적으로 설정한 경우 그 이미지가 잡히도록

즉, 정적 & 동적 둘 다 사용하고 싶다는 말이죠!

정적 이미지 적용

우선 초반에 ogImage 경로로 정적 이미지 경로를 잡아두었었어요.

// @/constants/metadata.ts
export const META = {
  ...
  ogImage: '/opengraph-image.png',
} as const;

metadataBase에 default 경로를 설정했기 때문에 <head>에는 www.bandiboodi.com/opengraph-image.png 라는 경로로 들어가게 돼요.

즉, 제가 설정한 대로라면, public 폴더 바로 아래에 존재해야 이미지를 찾을 수 있다는 말입니다.

🚨 이미지 경로를 꼭 잘 매우 엄청 잘 확인합시다

이렇게 잘 해두면 어느 페이지를 공유하더라도 정적 이미지가 미리보기에 뜨게 됩니다.

동적 이미지 적용

Next.js에서는 opengraph-image.tsx라는 파일 시스템 컨벤션을 제공해요. 공식 문서에 너무 잘 나와있어서 적용법에 대해서는 건너뛰고 제가 어려움을 겪었던 부분을 이야기해보려고 해요.

🥵 개발보다 어려웠다고 한 이유 1

오픈 그래프는 미리보기를 통해 볼 수 있는 내용이였죠? 그래서 제가 작성한 코드가 잘 반영되었는지 확인하려면 아래와 같은 과정들을 반복해야 했어요.

  • 커밋 → 푸쉬 → PR → PR에 대한 building → merge → main 브랜치에서 building → 카톡으로 보내서 확인

그래서 merge하고 빌딩될 때마다 하느님 부처님 예수님을 호출했었는데요

특히 opengraph-image가 잘 적용되었는지 확인하고 다시 트라이 하는 과정에서 시간이 많이 소요되었었어요.

🥲 로컬에서도 opengraph-image가 잘 적용되었는지 확인하는 방법

로컬에서 <head>를 열어보면 og:image를 찾을 수 있어요. 잘 적용되었는지 확인하기 위해서는 이미지 경로를 가져와서 브라우저를 통해 들어가보면 잘 적용되었는지 빠르게 확인할 수 있어요.

잘 적용된 경우 바로 확인이 가능하고, 경로가 이상하거나 파일 자체에 문제가 있다면 원하는 화면이 보이지 않아요.

🥵 개발보다 어려웠다고 한 이유 2

og:image 권장 사항

og:image에는 권장되는 이미지 크기와 용량에 대해서도 권장되는 사항들이 있어요.

  • 1200 x 630 크기 권장
  • 8MB 용량 이하 권장

그래서 크기를 맞추고, 용량은 최대한 작게 줄일 수 있을 정도로 줄이고 사용했습니다.

이미지 용량 줄이는 웹 사이트

동적으로 할 때 로컬 이미지는..

위에서 보셨다싶이 동적으로 생성한 og:image이미지를 깔고 그 위에 텍스트를 입력한 형태에요.

이미지 url에 로컬 이미지 경로를 넣어주었을 때 위에서 소개했던 방법으로는 확인할 수가 없었어요. 결국 저는 외부를 통해 이미지 경로를 가져와 이미지를 출력할 수 있었습니다.


이번 글에서는 메타데이터와 오픈그래프의 개념부터 제가 적용한 과정들을 소개해보았습니다. 누구 하나 이게 정답이라고 알려준 글 없이 다들 적용한 방법이 가지각색이라 적용하는 과정에서 어려움이 좀 있었던 거 같아요. 이번 기회를 통해 또 한번 새로운 도전을 시도하고 실패하고 실패하고 실패하고 끝내 성공할 수 있었습니다,, 🫡

References

profile
개발자와 사용자 모두의 눈👀을 즐겁게 하는 개발자가 되고 싶어요 :) 👩🏻‍💻

0개의 댓글