Next.js App Router에서 Dynamic Route params가 Promise로 들어오는 문제 해결 기록

유진·2025년 11월 20일

web

목록 보기
1/2

URL Shortener 프로젝트를 만들면서 /stats/[code] 페이지에서 특정 코드의 통계 정보를 불러오는 기능을 구현했다.
해당 부분에서 예상하지 못한 문제가 발생했다.

정리해보면, 다음과 같은 증상이 있었다.

  • 페이지는 열리지만 “Stats not found”가 표시된다.
  • 네트워크 탭에서 API 호출을 보면 /stats/undefined로 요청이 나간다.
  • Vercel 로그에서는 /stats/[code] 라우트가 정상적으로 호출되지만, 실제 코드 값이 전달되지 않는다.
  • 콘솔에서 다음 오류를 확인할 수 있었다:
Error: Route "/stats/[code]" used `params.code`. 
`params` is a Promise and must be unwrapped with `await` or `React.use()`

이 문제의 근본 원인은 Next.js App Router의 params 전달 방식 때문이다.


문제 원인: params가 Promise로 넘어온다

Next.js 13 이후 App Router를 사용할 경우, 서버 컴포넌트에서 dynamic route의 params가 가끔 Promise 형태로 전달된다.
로컬 환경에서는 Promise가 아닌 일반 객체처럼 보이기 때문에 문제를 알아채기 어렵다.

기존에 사용했던 코드:

export default async function StatsPage({ params }) {
  const { code } = params;   // 여기서 문제 발생
}

배포 후 콘솔을 찍어보면 다음과 같이 나온다.

params = Promise { { code: 'qSmO2J' }, ... }

즉, 구조분해 할당으로 바로 값에 접근할 수 없기 때문에 code가 undefined가 되고, API 요청 역시 /stats/undefined로 나가게 된다.


해결 방법: params를 await로 언래핑

문제 해결은 간단하다.
params 자체가 Promise이므로, 먼저 기다린 뒤 값을 꺼내야 한다.

수정된 코드:

export default async function StatsPage({ params }: { params: Promise<{ code: string }> }) {
  const { code } = await params;

  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/stats/${code}`, {
    cache: "no-store",
  });

  if (!res.ok) {
    return <div>Stats not found for {code}</div>;
  }

  const data = await res.json();

  return <StatsView code={code} data={data} />;
}

이렇게 수정한 뒤 다시 배포하니 문제는 바로 해결되었다.
더 이상 undefined가 전달되지 않고, API에서 받은 통계 정보가 정상적으로 렌더링된다.


추가로 배운 점

  1. App Router 기반에서 서버 컴포넌트의 dynamic params는 항상 Promise일 수 있다고 가정하는 것이 안전하다.
  2. 로컬 개발 환경과 Vercel 배포 환경이 완전히 동일하지 않을 수 있으니, params나 searchParams를 사용할 때는 콘솔로 형태를 확인하는 습관이 중요하다.
  3. 클라이언트 컴포넌트에서는 이런 문제가 발생하지 않지만, 서버 컴포넌트에서는 비동기 전달 이슈가 자주 등장할 수 있다.
  4. 이번 이슈는 Next.js 이슈 트래커에서도 언급될 정도로 흔히 발생하며, 버전에 따라 동작이 달라질 가능성이 있다.

마무리

처음에는 API URL 문제나 환경 변수 설정 오류를 의심했지만, 원인은 Next.js가 넘겨주는 params의 형태였다.
문제가 조금 돌아가는 듯 보였지만, 배포 환경에서 콘솔을 찍어보고 값을 직접 확인하면서 원인을 정확히 잡을 수 있었다.

동일한 구조로 Dynamic Route를 사용하는 경우라면, 서버 컴포넌트에서 await params를 적용하는 것만으로도 비슷한 문제를 예방할 수 있다.

profile
안드로이드... 좋아하세요?

0개의 댓글