nextjs๋ก ํด๋ณด๊ณ ์ถ์๋ ๊ฒ์ด ์๋ค๋ฉด Metadata API ๋ฅผ ํ์ฉํด์ ๊ฐ๋จํ๊ฒ metadata tag๋ฅผ ๋ฌ์์ฃผ๋ ๊ฒ ์๋๊น ์ถ๋ค.
์ค๋์ ๊ทธ ์ค ํ์ด์ง ํ๋์ ๋ฐ๋ผ ๋ค์ด๋ด๋ฏนํ๊ฒ ๋ฉํํ๊ทธ๋ฅผ ์์ฑํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ค๋ฃจ๊ณ ์ ํ๋ค.
Tripie ์ฑ์ ํฌ๊ฒ ๋ ๊ฐ์ง ์๋น์ค๋ฅผ ์ ๊ณตํ๋๋ฐ,
- ํธ๋ฆฌํ์์ ์คํฌ๋ํํ ์ฌํ์ง์ ๋ํ ํ, ์ง์ญ ์ ๋ณด๋ ์๋น ์ ๋ณด, ์ฌํ ์ผ์ ํ ๋ฑ์ ์ด๋ํ ์ ์๋ค.
- ์ฑ์งํผํฐ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์์ ๊ธฐํธ์ ๋ฐ๋ผ ์ฌํ์ผ์ ์ ์์ฑํด ์ค๋ค.
์ด ์ค ๋ฉํํ๊ทธ๋ก ์ผ์ ๋ฐ์ดํฐ๋ค์ด ๋ชจ๋ ์ค๋น๋ ์ฒซ ๋ฒ์งธ์ธ ์ฌํ์ง ์ ๋ณด์ ๋ํ ํ์ด์ง๋ค์ ๋ฉํํ๊ทธ๋ฅผ ์ถ๊ฐํด์ฃผ๊ณ ์ ํ๋ค.
๊ณต์๋ฌธ์๋ฅผ ์ดํด๋ณด๋ฉด, ๋ฉํํ๊ทธ๋ฅผ ๋ฌ ์ ์๋ ๋ฐฉ๋ฒ์ ๋๊ฐ์ง๋ค.
- Config ๊ธฐ๋ฐ : static metadata object๋ dynamic generateMetadata ํจ์๋ฅผ layout.js๋ page.js ํ์ผ์ exportํ๋ ๋ฐฉ์
- File ๊ธฐ๋ฐ : static์ด๋ dynamic ํ๊ฒ ์์ฑ๋ ์๋์ ํน์ ํ์ผ๋ค์ route segment์ ์ถ๊ฐ๋ ๋ฐฉ์
์์ ๋ช ์๋ ํ์ผ๋ค์ ํ์ฉํ๋ ๊ฒ์ด ์๋๋ฏ๋ก Config ๊ธฐ๋ฐ์ generateMetadata ํจ์๋ฅผ exportํ๋ ๋ฐฉ์์ ํํ๋ค.
โผ ์ฌ๊ธฐ์ ์ค์ํ ์ โผ
generateMetadata์ผ๋ก ์ ๊ณต๋ static์ด๋ dynamic ๋ฉํ๋ฐ์ดํฐ ๋ชจ๋ ServerComponent ์๋ง ์ง์๋๊ณ ,
Fetch ์์ฒญ์ generateMetadata, generateStaticParams, Layouts, Pages, and Server Components ์ฌ์ด์ ๋ชจ๋ ์๋์ ์ผ๋ก memoize๋๋ค. ๋ง์ฝ fetch ๊ฐ ๋ถ๊ฐ๋ฅํ๋ค๋ฉด react cache๋ฅผ ์ฌ์ฉํ ์ ์๋ค. nextjs๋ UI๋ฅผ ํด๋ผ์ด์ธํธ์๊ฒ stream ํ๊ธฐ ์ ์ generateMetadata ์์ ๋ฐ์ดํฐ fetch ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ธฐ ๋๋ฌธ์, stream๋๋ ์๋ต์ด <head> ํ๊ทธ๋ฅผ ํฌํจ๋ ์ ์๋๋ก ๋ณด์ฅํ๋ค.
tripie์ ์ํฐํด ์ข ๋ฅ๋ 3๊ฐ์ง๋ก ๋๋์ด articles/attractions/restaurant์ด ์๋ค.
์๋ฅผ ๋ค์ด https://tripie-mauve.vercel.app/regions/23c5965b-01ad-486b-a694-a2ced15f245c/articles/2c05edb7-0e01-4b16-99a1-ce304b4fa996๋ฅผ ์ดํด๋ณด๋ฉด
๋๊ฐ์ฌํค 3๋ฐ 4์ผ ์ฌํ ์ฝ์ค
์ฌํ์๋ค์๊ฒ ์ถ์ฒํ๋ ์ต์ ์ ์ผ์ ๊ณผ ๋ฃจํธ
tripie-mauve.vercel.app
๊ทธ๋ฆฌ๊ณ ํ์ด์ง ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
/regions
| /[regionId] (์ง์ญ ID, 23c5965b-01ad-486b-a694-a2ced15f245c)
| | [articles] (์ํฐํด ID, e96c2b79-849c-453f-8c0c-7950dc2754c7)
| | ใด page.tsx
| | [attractions] (๊ด๊ด๋ช ์ ID)
| | ใด page.tsx
| | [restaurant] (์๋น ID)
| | ใด page.tsx
| | [location] (๊ฒ์ ID, regions/์ผ๋ณธ)
| | ใด page.tsx
| ใด page.tsx
ใด page.tsx
attraction๊ณผ restaurant๋ ํ์ ์ด ์ ์ฌํ์ง๋ง attraction์ ์ข ๋ค๋ฅธ ํํ๋ค.
๋จผ์ /articles๋ฅผ ์ดํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ฐ์ดํฐ ํ์ ์ด๊ณ ,
export type ArticleData = {
body: BodyItemProps[];
header: null;
metadata: Metadata;
metadataContents: MetaDataContents;
placeId: string;
seoMetadata: null;
id: string;
};
export type MetaDataContents = {
image: TripieMetaImage;
title?: string;
description?: string;
};
์ด์ค metadataContents์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ์์ ์ด๋ค. ๋ฉํํ๊ทธ์ ์ค์ ํ ์ ์๋ ๊ฐ๋ค์ ๊ณต์ ๋ฌธ์์์ ํ์ธ ๊ฐ๋ฅํ๋ค.
์ฌ๊ธฐ์ ๊ถ๊ธํ ์ og:title์ด๋ ๊ทธ๋ฅ title์ด ์๋๋ฐ ์ฐจ์ด๊ฐ ๋ญ์ง?
๐น 1. Open Graph Meta Tags (og:)
- Purpose: Used for social media sharing (Facebook, Twitter, LinkedIn, etc.).
- Effect: Controls how links appear when shared (title, description, image, etc.).
- Prefix: Uses og: (e.g., og:title, og:image).
๐น 2. Regular Meta Tags (meta)
- Purpose: Used for SEO and general metadata (title, description, viewport settings, etc.).
- Effect: Helps search engines like Google understand and index content.
- Prefix: No prefix (e.g., meta name="description").
์ ๋ฆฌํ์๋ฉด,
open graph ํ๊ทธ๋ (og:)๋ฅผ ๋ถ์ฌ์ 'property="og:image"' ํํ๋ก ์ฌ์ฉ๋๋ฉฐ, ์์ ๋ฏธ๋์ด์ ๊ณต์ ๋ ๋งํฌ๊ฐ ๋ณด์ด๋ ๋ชจ์ต์ ๊ด์ฌํ๊ณ ,
์ผ๋ฐ meta ํ๊ทธ๋ name="title"๊ณผ ๊ฐ์ด ์ฌ์ฉ๋๋ฉฐ, ๊ตฌ๊ธ๊ณผ ๊ฐ์ด ๊ฒ์ ์์ง์ด ์ฝํ ์ธ ๋ฅผ ์ดํดํ๊ณ ์ธ๋ฑ์ฑ ํ๊ธฐ ์ํด ์ฌ์ฉ๋์ด SEO ํฅ์์ ๋์์ ์ค๋ค.
์ฆ, ๊ฒ์์์ง์ ๋ ธ์ถ์ํค๊ณ ์ถ๊ณ , ์์ ๋ฏธ๋์ด๋ก ๊ณต์ ํ ๋ ๋งํฌ ์นด๋๋ ๊พธ๋ฉฐ์ ๋ณด์ด๊ณ ์ถ๋ค๋ฉด ๋ ๋ค ์ฌ์ฉํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
// https://nextjs.org/docs/app/building-your-application/optimizing/metadata
type Props = {
params: Promise<{ regionId: string; articleId: string }>;
};
export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
// read route params
const regionId = (await params).regionId;
const articleId = (await params).articleId;
const { data } = await getArticleDetail('article', regionId, articleId);
const previousImages = (await parent).openGraph?.images || [];
const description = data?.metadataContents?.description ?? '';
const title = 'โ๏ธTripie | '+ data?.metadataContents?.title ?? '';
return {
title,
description,
openGraph: {
images: [data?.metadataContents.image.sizes?.full?.url ?? '', ...previousImages],
type: 'website',
url: `${API.BASE_URL}${ROUTE.REGIONS.href}/${regionId}/articles/${articleId}`,
title,
description,
siteName: 'Tripie',
},
};
}
๋ค์๊ณผ ๊ฐ์ด ์์ฑํด ์ฃผ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ฐฐํฌ๋ ์ฌ์ดํธ์ elements ํญ์ ์ด์ด๋ด์ ๋ฉํํ๊ทธ๊ฐ ์ ์ฉ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ด๋ฒ์๋ restaurants์ attractions๋ฅผ ์ดํด๋ณด๊ณ ์ ํ๋ค. ์ ์ฌํ๊ฒ generateMetadata๋ฅผ ๊ฐ ๊ฒฝ๋ก [restaurants]/[articleId]/page.tsx ๊ทธ๋ฆฌ๊ณ [attractions]/[articleId]/page.tsx์ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
type Props = {
params: Promise<{ regionId: string; articleId: string }>;
};
export async function generateMetadata({ params }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const regionId = (await params).regionId;
const articleId = (await params).articleId;
const { data } = await getArticleDetail('retaurant', regionId, articleId);
const previousImages = (await parent).openGraph?.images || [];
const title =
data?.source.names.primary ?? data?.source.names.ko ?? data?.source.names.en ?? data?.source.names.local ?? '';
const description = data?.source?.comment ?? '';
return {
title: `โ๏ธTripie | ${title}`,
description,
openGraph: {
images: [data?.source.image.sizes.full.url ?? '', ...previousImages],
type: 'website',
url: `${API.BASE_URL}${ROUTE.REGIONS.href}/restaurant/${regionId}/${articleId}`,
title: `โ๏ธTripie | ${title}`,
description,
siteName: 'Tripie',
},
};
}
https://www.opengraph.xyz/url/https%3A%2F%2Ftripie-mauve.vercel.app%2Fregions%2F23c5965b-01ad-486b-a694-a2ced15f245c%2Farticles%2F8c0aae20-b057-49c9-8420-cb8026c4a41a ์์ ์นด์นด์คํก ์ด์ธ์ ์์ ๋ฏธ๋์ด๋ก ๊ฐ๊ฐ ๊ณต์ ํ์ ๋ ๋ชจ์ต์ ํ์ธํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
(+์ถ๊ฐ) SEO ํฅ์์ ๋์์ ์ฃผ๋๊ฐ ํ์ธํ๋ ค๊ณ lighthouse๋ก ์งํ๋ฅผ ์ธก์ ํด ๋ณด๋, ํ์ฉ ๋ฐ์๋ค!
TODO'S
-์ด๋ ๊ฒ ํ๋ ๋ฉํ ํ๊ทธ ์ถ๊ฐ๋ ๋์๋ค! ๋ค๋ง ์ค๋ณต๋๋ ๋ถ๋ถ๋ค์ ์ ๊ฑฐํ ์ ์๋ ๋ฐฉ๋ฒ์ด ์๋ ์ฐพ์๋ณด๋, ์์์ page ๋ layout์ ๋ฉํํ๊ทธ๋ฅผ ํ์์ ํ์ผ์ ์์ฑํ๋ฉด ๋ฎ์ด์ธ ์ ์๋ค๋ ์ ์ด๋ค. ํ์ง๋ง ํ์ฌ ํ์ผ๋ค์ ๊ตฌ์กฐ์๋ ๋ถ์ ํฉํ๋ฏ๋ก ์ข ๋ ๊ณ ๋ฏผ์ ํ๊ณ ๊ฐ์ ๋ฐฉ์์ ์ฐพ์๋ด์ผ๊ฒ ๋ค. (ํด๋น ํฌ์คํธ์์ ์ ์ ํ๊ฒ ๋ฐ์ํด๋ณด์๋ค)
- ํ์ด์ง๋ณ๋ก ์ ์๊ฐ ์๋ค ๊ฐ๋ค ํ๋๋ฐ, ํ์คํ ๋์ ์ด์ ๋ณด๋ค๋ ๋๋ค. ์์ง ์ต์ ํ ๋จ๊ณ๋ ๋์ ์ ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ํ ์คํธ ์ฝ๋๋ค์ ๋จผ์ ์ง๊ณ , ๋ฆฌํํ ๋ง์ ์งํ ํ, ๊ทธ๋ค์์ ๋์ ํ ์์ ์ด๋ค.
REFERENCES
https://nextjs.org/docs/app/building-your-application/optimizing/metadata
Optimizing: Metadata | Next.js
Use the Metadata API to define metadata in any layout or page.
nextjs.org
'๐ฉ๐ปโ๐ป dev' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐ ๊ตฟ๋ฐ์ด CRA (Sunsetting Create React App) (1) | 2025.03.09 |
---|---|
[Nextjs] Dynamic metadata tag ์์ฑํ๊ธฐ (Part.2) (1) | 2025.02.26 |
Nextjs blurDataUrl dynamicํ๊ฒ ์ ๊ณตํ๊ธฐ (1) | 2025.02.15 |
polling mechanism์ผ๋ก react ref ํ์ธํ๊ธฐ (0) | 2025.01.22 |
puppeteer aws lambda์ ๋ฐฐํฌํ๊ธฐ (0) | 2024.11.02 |