๐ [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();
}
}
๊ฐ์ฅ ์ธ๋ถ์ ์ผ๋ก ์ ํด์ฃผ์ด์ผ ํ๋ ๋ฉํ๋ฐ์ดํฐ๋ ์์ ๊ฐ์ด ์๋ํ๋๋ก ํด๋ดค์ต๋๋ค.
'React > Nextjs' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[NextJS, SEO] AppRouter Dynamic Sitemap.xml ์์ฑํ๊ธฐ (0) | 2024.11.22 |
---|---|
Vercel - Nextjs ๋ฐฉ๋ฌธ์ ์ ์ง๊ณ, ์น ๋ถ์ ์ด์ฉํ๊ธฐ (0) | 2024.10.10 |
[Nextjs] react-query๋ฅผ ์ด์ฉํ ์๋ฒ ๋ฐ์ดํฐ Prefetch ๋ฐฉ๋ฒ (0) | 2024.09.12 |
[Nextjs] searchParams๋ก URL ์ฟผ๋ฆฌ ์คํธ๋ง ํ์ฑํ๊ธฐ (0) | 2024.09.09 |
[Nextjs] Dynamic Route, ๋์ ๋ผ์ฐํ (0) | 2024.08.03 |
๋๊ธ