동적 라우팅(Dynamic Routing)과 메타데이터(SEO)
상품 상세 페이지나 블로그 포스팅처럼 ID별로 동적으로 변하는 페이지를 만들 때는 [folderName] 문법을 사용합니다. 나아가, 각 페이지마다 검색 엔진(SEO) 최적화 메타 태그를 동적으로 부여하여 검색 노출을 극대화할 수 있습니다.
동적 SEO 세팅의 강력함 (generateMetadata)
과거 CSR 방식에서는 자바스크립트가 로딩된 후에야 문서 제목(Title)을 바꿀 수 있어, 검색 엔진이나 카카오톡 공유 시 제목과 이미지가 제대로 나오지 않는 문제가 있었습니다. Next.js에서는 generateMetadata 함수를 통해 서버 측에서 완벽하게 <meta> 태그를 생성하여 클라이언트로 쏴줍니다.
// ==========================================
// 📂 app/products/[id]/page.tsx
// 동적 라우팅 페이지 및 동적 SEO 메타 데이터 생성
// ==========================================
import { Metadata } from 'next';
// 동적 라우팅 페이지는 params 객체를 비동기(Promise) 형태로 전달받습니다.
type Props = {
params: Promise<{ id: string }>;
}
// 1️⃣ 동적 메타 데이터 생성 (카카오톡 공유, 구글 검색 엔진 최적화용)
// 컴포넌트 렌더링 전에 서버에서 실행되어 <head> 태그에 주입됩니다.
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
// SEO를 위해 DB에서 해당 상품 정보 조회
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
return {
title: `${product.name} 구매하기 | Minstudio Store`,
description: product.description,
openGraph: {
title: product.name,
images: [product.thumbnail_url], // 카톡 공유 시 나타나는 썸네일
},
};
}
// 2️⃣ 실제 화면 렌더링 컴포넌트
export default async function ProductDetailPage({ params }: Props) {
// Promise 형태의 params를 await로 풀어냅니다. (Next.js 15+ 권장)
const { id } = await params;
// 메타데이터에서 조회했던 데이터를 한 번 더 조회합니다.
// ※ 걱정 마세요! Next.js가 똑똑하게 두 번째 요청은 가로채서 캐시된 응답을 줍니다. (Deduplication)
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
return (
<div className="max-w-4xl mx-auto p-8">
<h1 className="text-3xl font-bold mb-4">상품 번호: {id}</h1>
<div className="flex gap-8">
<img src={product.image} alt={product.name} className="w-1/2 rounded-2xl shadow" />
<div>
<h2 className="text-2xl font-bold">{product.name}</h2>
<p className="text-xl text-blue-600 font-bold mt-4">{product.price.toLocaleString()}원</p>
<button className="mt-8 px-8 py-3 bg-black text-white font-bold rounded-full w-full">
장바구니 담기
</button>
</div>
</div>
</div>
);
}