6 분 소요

Next에서 metadata, og 태그, 공유, 홈 화면 추가 세팅


관련 PR
[feat] metadata 추가(#162) #164
[fix] 기본 메타데이터 안 나오는 오류 해결
[fix] 동적 생성 og 데이터 안 보이는 이슈(#171) #172
[fix] 아이폰 카카오 공유하기 오류 해결(#173) #174


🔥메타데이터 정보 상수화

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

metadata.ts

우리 사이트의 기본 metadata를 상수화하였다.

export const META = {
  title: "CON:SEAT - See it before you Sit",
  siteName: "CON:SEAT | 콘시트",
  shortName: "CON:SEAT", // 홈 화면 추가할 때 사용
  description: "한눈에 확인하는 콘서트장 시야",
  keyword: [
    "콘시트",
    "conseat",
    "concert seat",
    "콘서트",
    "콘서트 시야",
    "시야",
    "KSPO DOME",
    "체조 경기장",
    "올림픽 체조 경기장",
  ],
  url: "https://concertseat.site",
  locale: "ko_KR",
  googleVerification: "---",
  ogImage: "/og/main-og.png", // public/og/main-og.png 가져오기
  twitterSite: "@con_see_at",
  twitterCreator: "@con_see_at",
} as const;


googleVerification 얻는 방법

1. 아래 사이트로 이동
https://search.google.com/search-console


2. 도메인 추가
“URL 접두어”로 사이트 url 추가(도메인도 상관없음)

  • 당장 빠르게 시작해서 /blog, /app 등 특정 경로만 보면 된다 -> URL 접두어
  • 앞으로 서브도메인·프로토콜을 모두 커버하고 싶고, DNS에도 손댈 수 있다 -> 도메인 속성


3. HTML 파일 다운

  • “DNS 레코드를 통해 도메인 소유권 확인” 창의 3번 항복에서 “복사” 버튼을 클릭해 TXT 레코드를 복사
  • 이미 등록이 되어 있다면 좌측 상단 “햄버거 버튼” -> “설정” -> “소유권 인증” -> “도메인 이름 공급업체” -> TXT 복사



🔥메타데이터 만드는 함수

getMetadata.ts

[인자]

  • title: title, og:title, og:siteName, og:images:alt, twitter:title, twitter:images:alt
  • description: description, og:description, twitter:description
  • asPath: alternates.canonical, og:url
  • ogImage: og:image, twitter:image
import type { Metadata } from "next";
import { META } from "@/constants/metadata";

interface GenerateMetadataProps {
  title?: string;
  description?: string;
  asPath?: string; // 동적 메타데이터 생성 시 필수 입력
  ogImage?: string;
}

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 : META.url;
  const OG_IMAGE = ogImage || META.ogImage;

  const metadata: Metadata = {
    metadataBase: new URL(META.url),
    alternates: {
      canonical: PAGE_URL, // 다른 URL, 같은 화면일 때 URL을 한 곳으로 몰아 SEO 점수 높임
    },
    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,
          width: 1200,
          height: 630,
          alt: TITLE,
        },
      ],
    },
    verification: {
      google: META.googleVerification,
    },
    twitter: {
      card: "summary", // summary_large_image, app, player
      site: META.twitterSite,
      creator: META.twitterCreator,
      title: TITLE,
      description: DESCRIPTION,
      images: [
        {
          url: OG_IMAGE,
          width: 1200,
          height: 630,
          alt: TITLE,
        },
      ],
    },
    appleWebApp: {
      capable: true, // standalone 모드 허용
      title: META.shortName, // 홈 화면 아이콘 아래에 표시될 이름
      statusBarStyle: "default", // 앱 실행 시 상단(status bar) 스타일
    },
  };

  return metadata;
};


layout.ts에 메타데이터 추가

import { getMetadata } from "@/utils/getMetadata";

export const metadata: Metadata = getMetadata(); // metadata 추가

const RootLayout = async ({
  children,
}: Readonly<{ children: React.ReactNode }>) => {};



🔥1. 웹 사이트 title

각 메타데이터 title에 맞게 잘 바뀐다.

[결과 화면]

[홈 페이지]

Image

[시야 결과 페이지]

Image



🔥2-1. 카카오톡 홈 페이지 링크 전송

[문제점]
제일 처음 페이지를 전송했을 때 og 이미지가 보이지 않았다.

Image


[원인]
Facebook, Twitter, 카카오 등은 SVG를 제대로 렌더링하지 않거나 무시해 버린다.


[해결 방법]
og 이미지를 SVG에서 PNG로 변환하여 해결했다.

Image


[추가 구현사항]
로고 해상도가 깨져보여서 피그마에서 2x로 추출했다.



🔥2-2. 카카오톡 결과 페이지 링크 전송

[구현 사항]
결과 페이지는 동적으로 메타데이터를 만들어야 하는 페이지다. 기획 내용에 맞게 변경했다. generateMetadata만 페이지 파일에 적어두면 동적으로 메타데이터가 생성된다.

아까 만들어두었던 getMetadata 함수를 사용하여 변경해보자.

home/[stadiumId]/single/[seatingId]/page.tsx

import { META } from "@/constants/metadata";
import { getMetadata } from "@/utils/getMetadata";

export async function generateMetadata({ params }): Promise<Metadata> {
  const { stadiumId, seatingId } = await params;

  const { data: stadiumList } = await getStadiumList();
  const seatInfo = await getSeatingReviews(Number(seatingId));
  const stadium = stadiumList.active?.find(
    (s) => s.stadiumId === Number(stadiumId)
  );

  if (!stadium) notFound();

  const title = `${stadium.stadiumName} | ${seatInfo.floorName} ${
    seatInfo.sectionName
  }${seatInfo.seatingName ? ` ${seatInfo.seatingName}` : ""} 시야`;
  const description = "CON:SEAT에서 구역별 시야를 확인해보세요";
  const ogImage = seatInfo.reviews?.[0]?.images?.[0] || undefined;

  return getMetadata({
    title,
    description,
    ogImage,
  });
}

const ResultPage = async ({ params }) => {};


[문제점]
동적으로 변경해주었는데 기본 메타데이터로 나왔다.

Image


[원인]
카카오 디벨로퍼 문의글에서 해당 내용을 찾을 수 있었다. 스크랩할 주소와 og:url 설정이 다른 경우 og:url 주소의 메타 정보를 조회한다.

현재 og:url는 default 값인 https://concertseat.site 이고, 이 url의 메타데이터인 default 값이 보였던 것이다.


[해결방법]

1. og:url의 값을 바꿔주는 asPath 값을 이 페이지 url로 변경

export async function generateMetadata({ params }): Promise<Metadata> {
  // ···

  const title = `[${stadium.stadiumName}] ${seatInfo.floorName} ${
    seatInfo.sectionName
  }${seatInfo.seatingName ? ` ${seatInfo.seatingName}` : ""} 시야`;
  const description = "CON:SEAT에서 구역별 시야를 확인해보세요";
  const asPath = `${META.url}/home/${stadiumId}/single/${seatingId}`; // asPath 현재 경로로 정의
  const ogImage = seatInfo.reviews?.[0]?.images?.[0] || undefined;

  return getMetadata({
    title,
    description,
    asPath, // asPath 추가
    ogImage,
  });
}


2. 캐싱 제거

그래도 이전 og 태그가 보인다면 이는 캐싱되어 있는 것이다. 빠른 og 정보를 가져오기 위해 카카오가 캐싱한다. 함수 내용이 바뀌었다면 캐싱을 수동으로 제거해줘야 한다.

아래 사이트에서 사이트 url을 넣고 “디버그” -> “캐시 초기화”를 눌러 캐시를 제거한다.

https://developers.kakao.com/tool/debugger/sharing



🔥2-3. 카카오톡 결과 공유하기

[구현 사항]

1. KakaoScript.tsx 생성

앱 라우터에서 Kakao.init이 제대로 실행되지 않았다. KakaoScript.tsx를 클라이언트 컴포넌트로 변경해준 후 스크립트를 다 불러오는 즉시 Kakao.init을 적용시켜 주는 방법으로 해결하였다.

"use client";

import Script from "next/script";
import { PUBLIC_ENV } from "@/config/env";

const KakaoScript = () => {
  const onLoad = () => {
    const Kakao = window.Kakao;
    Kakao.init(PUBLIC_ENV.kakaoApiKey); // 카카오 디벨로버 JavaScript 키
  };

  return (
    <Script
      src="https://developers.kakao.com/sdk/js/kakao.js"
      async
      onLoad={onLoad}
    />
  );
};

export default KakaoScript;


2. 공유하기 정보 커스텀하기

  • window.Kakao.Share: Link에서 Share로 변경됨
  • 메시지 구성 방법: 카카오 디벨로퍼에서 확인 가능
    ‘sendDefault()’ | ‘sendScrap()’ | ‘sendCustom()’
  • 기본 메시지 템플릿(objectType): 카카오 디벨로퍼에서 확인 가능
    ‘feed’ | ‘list’ | ‘location’ | ‘commerce’ | ‘text’


ShareArea.tsx

const handleShareKakao = () => {
  window.Kakao.Share.sendDefault({
    //2022.05.30부터 Kakao.Link에서 Kakao.Share로 변경
    objectType: "feed",
    content: {
      title,
      description,
      imageUrl,
      link: {
        mobileWebUrl: url,
        webUrl: url,
      },
    },
    buttons: [
      {
        title: "자세히 보기",
        link: {
          mobileWebUrl: url,
          webUrl: url,
        },
      },
    ],
  });
};


[결과 화면]

Image



🔥3-1. 트위터 홈 페이지 링크 전송

참고 : About X Cards

[구현 사항]

  • twitter:card: 필수 값(summary, summary_large_image)
  • summary에서 카드의 이미지는 최소 144x144 픽셀 또는 최대 4096x4096 픽셀의 1:1 종횡비를 지원한다.
  • JPG, PNG, WEBP, GIF 형식이 지원된다. SVG는 지원되지 않는다.
twitter: {
  card: "summary", // summary_large_image, app, player
  site: META.twitterSite,
  creator: META.twitterCreator,
  title: TITLE,
  description: DESCRIPTION,
  images: [
    {
      url: OG_IMAGE, // png 형식
      width: 144,
      height: 144,
      alt: TITLE,
    },
  ],
},


twitter:card summary_large_image 예시

Image


twitter:card summary 예시

Image



🔥3-2. 트위터 공유하기

[구현 사항]
https://twitter.com/intent/tweet?url= 뒤에 인코딩 된 url과 text를 넣으면 원하는 게시글과 링크를 자동으로 넣을 수 있다.

ShareArea.tsx

const handleShareTwitter = () => {
  const text = encodeURIComponent(`${title}\n${description}\n`);
  window.open(
    `https://twitter.com/intent/tweet?url=${encodeURIComponent(
      url
    )}&text=${text}`,
    "_blank"
  );
};


[결과 화면]
정의한 text가 자동 기재된다.


[주의 사항]
트위터도 빠른 og 태그 렌더링을 위해 캐싱한다. 카카오는 캐시 초기화를 할 수 있지만 트위터는 1~3일마다 자체적으로 캐시를 초기화하는 것 같다. 바로 안 바뀌더라도 며칠 기다려보자😁



🔥4. 홈 화면 추가

참고 : favicon, icon, and apple-icon

[구현 사항]

  • capable: true: 홈 화면에 추가 시 주소창 없이 앱처럼 실행 홈 화면에 추가하면 사파리가 주소 표시줄·탭 바 없이 전체화면 모드로 실행
  • title: 앱 이름
  • statusBarStyle: "default": 앱 실행 시 상단(status bar) 스타일
    default: 투명 배경 + 다크 텍스트
    black: 검은 배경 + 흰색 텍스트
    black-translucent: 컨텐츠가 상태바 뒤까지 확장되고, 흰색 텍스트 사용
appleWebApp: {
  capable: true, // standalone 모드 허용
  title: META.title, // 홈 화면 아이콘 아래에 표시될 이름
  statusBarStyle: "default", // 앱 실행 시 상단(status bar) 스타일
},


[문제점]

  • title을 그대로 적었더니 앱 이름이 너무 길어졌다.
  • 아이폰은 홈 화면 추가 아이콘을 따로 지정해주어야 한다.

[아이폰] - 문제

Image


[갤럭시] - 잘 나옴

Image


[해결방법]

1. title 따로 지정

appleWebApp: {
  capable: true,
  title: META.shortName, // CON:SEAT로 정의
  statusBarStyle: 'default',
},


2. apple icon 지정

Next.js가 app 디렉터리 최상단에 특정 이름의 아이콘 파일을 두면, 별도 설정 없이도 자동으로 <link> 태그를 생성해 준다.

  • favicon.ico : 브라우저 탭의 아이콘
  • icon.png : PWA, 브라우저의 마스커블 아이콘
  • apple-icon.png : iOS 사파리에서 “홈 화면에 추가” 했을 때 사용하는 Apple Touch Icon

Image


[결과 화면]

Image



🔥5. 슬랙 & 디스코드

[슬랙]

Image


[디스코드]

Image



🔥마무리

Next에서 중요한 SEO와 관련된 부분이라 많은 신경을 썼다. 또 카카오와 트위터 공유하기 기능도 있었기에 디자이너와 PM과 계속 대화를 나누며 문구와 사진을 변경했다.

트러블 슈팅을 굉장히 많이 한 것 같은데 누군가에게 도움이 되길 바란다.🤗

카테고리:

업데이트:

댓글남기기