๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
React/Nextjs

[NextJS, SEO] AppRouter Dynamic Metadata ์ƒ์„ฑํ•˜๊ธฐ

by LasBe 2024. 11. 21.
๋ฐ˜์‘ํ˜•

๐Ÿ“’ [NextJS, SEO] AppRouter Dynamic Metadata ์ƒ์„ฑํ•˜๊ธฐ


๋™์ ์ธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽ˜์ด์ง€๋งˆ๋‹ค ๋งž์ถคํ™”๋œ ๋ฉ”ํƒ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ด๋ฅผ ํ†ตํ•ด ๊ฒ€์ƒ‰ ์—”์ง„์ด ํŽ˜์ด์ง€์˜ ๋ชฉ์ ๊ณผ ๋‚ด์šฉ์„ ๋” ์ •ํ™•ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด SEO๋ฅผ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ณ ,

example.com/post/123์™€ example.com/post/456์ฒ˜๋Ÿผ ๊ฐ™์€ ํŽ˜์ด์ง€์—ฌ๋„ ๋…๋ฆฝ์ ์œผ๋กœ ์ƒ‰์ธ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์ •์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ

NextJS์—์„œ ์ •์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}

๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ layout.tsx ํ˜น์€ page.tsx์—์„œ Metadata ๊ฐ์ฒด๋ฅผ ๋‚ด๋ณด๋‚ด๊ธฐ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ๋™์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ƒ์„ฑ

export function generateMetadata(): Metadata {
  return {
    title: 'ํ™ˆํŽ˜์ด์ง€',
  }
}

// or

export async function generateMetadata(
  { params, searchParams }: Props, parent: ResolvingMetadata
): Promise<Metadata> {
  const data = await fetch('...');
  return {
    title: data.title,
  }
}

๊ฐ layout.tsx ํ˜น์€ page.tsx์—์„œ MetaData, Promise<Metadata>๋ฅผ returnํ•˜๋Š” generateMetadata() ํ•จ์ˆ˜๋ฅผ ๋‚ด๋ณด๋‚ด๊ธฐ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

๋ฌผ๋ก  ์ด ํ•จ์ˆ˜๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ์ฒ˜๋ฆฌ๋˜๋ฉฐ ํŽ˜์ด์ง€์ฒ˜๋Ÿผ path parameter๋‚˜ query parameter๋„ props๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด ๋™์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ์— ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ๋ถ€๋ชจ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋„ ResolvingMetadata๋กœ ๋ฐ›์•„์™€ ๋ฎ์–ด์”Œ์šฐ๋Š” ๋“ฑ ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ์‹ค์‚ฌ์šฉ ์˜ˆ์‹œ

์ ์ ˆํ•œ ์˜ˆ์‹œ๊ฐ€ ๋  ์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ, ์ œ๊ฐ€ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import { Metadata } from "next";

export class SeoUtil {
  static metadata(brand?: string, isGenerateDesc?: boolean) {
    const title = `${brand || "์˜คํ”ˆํ”„์ฐจ"} | ํ”„๋žœ์ฐจ์ด์ฆˆ ์ •๋ณด๋ถ„์„`;
    const defaultDesc = `๊ณต์ •๊ฑฐ๋ž˜์œ„์›ํšŒ์˜ ๊ฐ€๋งน์‚ฌ์—… ์ •๋ณด๊ณต๊ฐœ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฐฝ์—… ์ „, ํ”„๋žœ์ฐจ์ด์ฆˆ ๋ณธ์‚ฌ ์ •๋ณด, ๋ธŒ๋žœ๋“œ์˜ ๋งค์ถœ, ๊ฐ€๋งน์  ์ˆ˜, ์ธํ…Œ๋ฆฌ์–ด ๊ธˆ์•ก, ์ฐฝ์—… ๋น„์šฉ ์ •๋ณด๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.`;
    const generatedDesc = `${brand}์˜ ๋ณธ์‚ฌ ์ •๋ณด, ๋ธŒ๋žœ๋“œ์˜ ๋งค์ถœ, ๊ฐ€๋งน์  ์ˆ˜, ์ธํ…Œ๋ฆฌ์–ด ๊ธˆ์•ก, ์ฐฝ์—… ๋น„์šฉ ์ •๋ณด๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ํ™•์ธํ•˜์„ธ์š”.`;
    const convertDesc = () => {
      if (brand && isGenerateDesc) return generatedDesc;
      return defaultDesc;
    };
    const description = convertDesc();

    const metadata: Metadata = {
      title,
      description,
      keywords: "ํ”„๋žœ์ฐจ์ด์ฆˆ, ๊ฐ€๋งน์‚ฌ์—…, ํ”„๋žœ์ฐจ์ด์ฆˆ ์ •๋ณด๋ถ„์„, ํ”„๋žœ์ฐจ์ด์ฆˆ ๋งค์ถœ, ํ”„๋žœ์ฐจ์ด์ฆˆ ์ฐฝ์—…, ์ฐฝ์—…, ์ฐฝ์—… ๋น„์šฉ",
      openGraph: {
        title,
        description,
        siteName: "์˜คํ”ˆํ”„์ฐจ | ํ”„๋žœ์ฐจ์ด์ฆˆ ์ •๋ณด๋ถ„์„",
        locale: "ko_KR",
        type: "website",
        url: "https://openfranchise.kr",
        images: {
          url: "/og-image.jpg",
        },
      },
      verification: {
        google: "...",
        other: {
          "naver-site-verification": "...",
        },
      },
    };
    return metadata;
  }
}

ํฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์•„๋‹ˆ์—ฌ์„œ ์„ธ๋ถ€์ ์œผ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š๊ณ  ์–ด๋Š์ •๋„ ํ…œํ”Œ๋ฆฟ์„ ๋งž์ถฐ Metadata๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

 

export const metadata = SeoUtil.metadata();

๊ธฐ๋ณธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ณ 

 

type BrandPageParams = {
  params: {
    name: string;
  };
};

export async function generateMetadata({ params: { name } }: BrandPageParams) {
  try {
    const brandResponse = await BrandService.getBrand(name);
    const { brand, head } = brandResponse?.payload;
    const metadata = SeoUtil.metadata(`${brand?.brandNm} - ${head.jnghdqrtrsConmNm}`, true);
    return metadata;
  } catch (error) {
    console.error(error);
    return SeoUtil.metadata();
  }
}

๊ฐ€์žฅ ์„ธ๋ถ€์ ์œผ๋กœ ์ •ํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ์œ„์™€ ๊ฐ™์ด ์ž‘๋™ํ•˜๋„๋ก ํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€


์˜คํ”ˆ ์ฑ„ํŒ