Frontend/Next.js

Dynamic Routes: 동적 파라미터([id]) 처리와 상세 페이지

미니임 2026. 3. 13. 22:48

 

현대 웹 서비스에서 '상세 페이지'가 없는 서비스는 상상하기 어렵습니다. 수만 개의 상품이 있는 이커머스나 수백 명의 글이 올라오는 블로그에서, 각 페이지를 일일이 파일로 만드는 것은 불가능에 가깝죠. 이때 개발자의 생산성을 폭발적으로 높여주는 기술이 바로 **동적 루팅(Dynamic Routes)**입니다.

오늘은 Next.js의 핵심 기능인 동적 파라미터([id]) 처리 방식을 살펴보고, 이를 통해 어떻게 수천 개의 상세 페이지를 효율적으로 관리할 수 있는지 깊이 있게 다뤄보겠습니다.


동적 루팅: 웹의 유연성을 담당하는 '가변적 주소'

전통적인 정적 루팅이 특정 주소에 특정 파일을 1:1로 매칭한다면, 동적 루팅은 주소의 일부분을 변수처럼 사용하는 방식입니다.

예를 들어 /product/1, /product/2와 같은 주소가 있을 때, 우리는 1이나 2라는 값을 데이터베이스에서 조회할 '키'로 활용합니다. Next.js는 파일 시스템 기반 라우팅을 사용하므로, 폴더 이름을 [id]와 같이 대괄호로 감싸는 것만으로도 이 마법을 시작할 수 있습니다.

일상 속의 비유: 우편함과 번호표

동적 루팅은 마치 아파트의 **'공용 무인 택배함'**과 같습니다. 모든 입주민을 위해 수백 개의 택배함을 따로 만들 필요 없이, 하나의 택배함 구역을 만들어두고 도착한 택배의 '호수(ID)'에 따라 해당하는 물건을 꺼내주는 것과 같은 원리입니다. 여기서 '호수'가 바로 동적 파라미터인 id가 됩니다.


실전 Hands-on: 주문 관리 시스템 상세 페이지 구현

단순한 예제 대신, 실제 관리자 페이지에서 쓰일 법한 '주문 상세 내역(Order Details)' 조회 로직을 통해 구현 방법을 알아보겠습니다.

1. 폴더 구조 설계

app/orders/[id]/page.tsx 경로로 파일을 생성합니다. 여기서 [id] 폴더명이 곧 변수명이 됩니다.

2. 코드 구현 및 상세 분석

TypeScript
 
// app/orders/[id]/page.tsx

interface OrderProps {
  params: {
    id: string;
  };
}

// 가상의 주문 데이터 페칭 함수
async function getOrderData(orderId: string) {
  // 실제 환경에서는 API 호출이나 DB 쿼리가 발생합니다.
  const res = await fetch(`https://api.example.com/orders/${orderId}`, {
    next: { revalidate: 3600 } // 1시간마다 캐시 갱신
  });
  
  if (!res.ok) return null;
  return res.json();
}

export default async function OrderDetailPage({ params }: OrderProps) {
  // 1. URL로부터 전달된 동적 파라미터(id)를 받아옵니다.
  const { id } = params;
  
  // 2. 해당 ID를 사용해 상세 데이터를 조회합니다.
  const order = await getOrderData(id);

  // 3. 데이터가 없을 경우에 대한 예외 처리
  if (!order) {
    return (
      <div className="p-10 text-center">
        <h1 className="text-2xl font-bold">주문 정보를 찾을 수 없습니다.</h1>
        <p className="text-gray-500">주문 번호 #{id}는 유효하지 않은 번호입니다.</p>
      </div>
    );
  }

  // 4. 실제 비즈니스 로직 렌더링
  return (
    <main className="max-w-4xl mx-auto p-8">
      <header className="border-b pb-4 mb-6">
        <h1 className="text-3xl font-extrabold text-blue-600">주문 상세 내역</h1>
        <p className="text-sm text-gray-400 mt-2">Order ID: {id}</p>
      </header>

      <section className="bg-white shadow-md rounded-lg p-6">
        <div className="flex justify-between mb-4">
          <span className="font-semibold">결제 상태</span>
          <span className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm">
            {order.status}
          </span>
        </div>
        
        <div className="space-y-4">
          {order.items.map((item: any) => (
            <div key={item.id} className="flex justify-between border-b py-2">
              <span>{item.name} (x{item.quantity})</span>
              <span className="font-mono">{item.price}원</span>
            </div>
          ))}
        </div>

        <div className="mt-8 text-right font-bold text-xl">
          총 결제 금액: {order.totalAmount}원
        </div>
      </section>
    </main>
  );
}

트러블슈팅: 자주 겪는 실수와 해결책

1. 파라미터 타입 불일치

  • 증상: params.id를 숫자로 생각하고 계산 로직에 넣었을 때 에러 발생.
  • 원인: URL 파라미터는 항상 문자열(string) 타입으로 들어옵니다.
  • 해결: 숫자 계산이 필요하다면 parseInt(id, 10)를 사용해 명시적으로 형변환을 해줘야 합니다.

2. 404 페이지 제어

  • : 데이터가 존재하지 않을 때 단순히 빈 화면을 보여주는 것보다, Next.js에서 제공하는 notFound() 함수를 호출하여 미리 정의된 not-found.tsx 페이지로 리다이렉트시키는 것이 UX 측면에서 훨씬 우수합니다.

트레이드오프(Trade-offs): 정적 생성 vs 동적 렌더링

동적 루팅을 사용할 때는 성능과 최신성 사이의 선택이 필요합니다.

  • generateStaticParams 활용 (SSG):
    • 장점: 미리 빌드 타임에 상세 페이지들을 HTML로 만들어두어 접속 속도가 매우 빠릅니다.
    • 단점: 상품이 수백만 개라면 빌드 시간이 너무 오래 걸릴 수 있습니다.
  • 실시간 데이터 페칭 (SSR):
    • 장점: 항상 최신 상태의 데이터를 보여줄 수 있습니다.
    • 단점: 사용자가 접속할 때마다 서버가 일을 해야 하므로 응답 속도가 조금 느려질 수 있습니다.

권장 전략: 자주 바뀌지 않는 콘텐츠(블로그)는 정적 생성을, 실시간 재고나 상태가 중요한 서비스(이커머스 주문 내역)는 동적 렌더링을 선택하는 것이 좋습니다.


결론 및 제언

동적 루팅은 단순히 주소를 가변적으로 만드는 기술을 넘어, 데이터 지향적인 웹 서비스를 설계하는 기초가 됩니다. [id]라는 이름의 폴더 하나가 수만 개의 유니크한 페이지를 대변할 수 있다는 점은 현대 프레임워크가 주는 가장 큰 선물 중 하나입니다.

반응형