minstudio

동적 라우팅(Dynamic Routing)과 메타데이터(SEO)

상품 상세 페이지나 블로그 포스팅처럼 ID별로 동적으로 변하는 페이지를 만들 때는 [folderName] 문법을 사용합니다. 나아가, 각 페이지마다 검색 엔진(SEO) 최적화 메타 태그를 동적으로 부여하여 검색 노출을 극대화할 수 있습니다.

🔍 동적 라우팅과 동적 SEO 메타 태그 폴더 구조 app/ products/ [id]/ 📄 page.tsx 매칭 브라우저 결과 (URL: /products/42) <head> <title>에어팟 프로 2세대</title> <meta property="og:image" ...> </head> <body> 화면 렌더링 (await params.id) </body>

동적 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>
  );
}
동적 라우팅(Dynamic Routing)과 메타데이터(SEO) | Minstudio