개발/Next.js

Next.js 15에서 params가 Promise로 바뀌면서 생긴 빌드 에러 해결기

dev_jiwonpark 2025. 9. 21. 19:54

Next.js 15로 프로젝트를 올린 뒤, 평소처럼 params에서 id를 꺼내려다가 갑자기 빌드 에러가 터졌습니다.
분명 이전 버전에서는 아무 문제 없던 코드인데, 이번에는 타입 에러가 발생하면서 빌드조차 되지 않았습니다.

처음엔 단순한 오타나 설정 문제라고 생각했지만, 원인을 찾아보니 Next.js 15부터 params와 searchParams가 Promise로 바뀌었다는 사실을 알게 되었습니다.

이번 글에서는 제가 직접 겪은 빌드 에러 상황을 예제로 보여드리고, 왜 이런 변화가 있었는지, 어떻게 수정했는지, 그리고 이 과정에서 어떤 개념을 다시 정리하게 되었는지를 기록해보려고 합니다.


문제상황은 다음과 같았습니다!

export default async function Page({
  params,
}: {
  params: { productId: string; userId: string };
}) {

  const { productId, userId } = params; // ❌ 여기서 빌드 에러
  
  return <div>{productId} / {userId}</div>;
}

 

빌드 시 아래와 같은 에러가 발생했습니다.. : (

Type '{ id: string; userId: string; }' is not assignable to type 'Promise<any>'.

왜 그런걸까 찾아보니 공식문서의 설명을 확인할 수 있었습니다!

Next.js의 15버전의 주요 변경점 중 하나는 Async Request APIs 입니다.

 

기존에 동기적으로 접근 가능했던 여러 런타임 API들이 전부 비동기(Promise) 기반으로 변경되었습니다.

  • cookies()
  • headers()
  • draftMode()
  • params, searchParams (Page, Layout, Route, Metadata 등)
더보기

Next.js 15에서 cookies()나 headers() 같은 API들이 비동기로 전환됐다는 것은, 이 API들이 다른 작업을 막지 않고 처리되도록 동작 방식이 바뀌었음을 의미합니다.

동기 코드에서는 함수가 끝날 때까지 전부 기다려야 했다면, 비동기 코드에서는 필요한 순간에만 await를 통해 결과를 받아오고, 그동안 다른 로직을 진행할 수 있게 됩니다.
(출처 : https://reactnext-central.xyz/blog/nextjs/nextjs-15x-versionup-code )

즉 아래의 Before 와 같이 쓰여졌던 코드를 After 처럼 바꿔야합니다.

// Before
export default function Page({ params }) {
  const { slug } = params
}

// After
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
}

 

따라서 저는 다음과 같이 Promise 타입을 직접 명시하고 await으로 값에 접근해 에러를 해결할 수 있었습니다.

interface PageProps {
  params: Promise<{ id: string; userId: string }>;
}

export default async function Page({ params }: PageProps) {
  const { productId, userId } = await params;
  return <div>{productId} / {userId}</div>;
}

 

그럼 다시 Next.js 15버전 업그레이드 내용으로 돌아가서! 

왜 Next.js 는 15버전에서 비동기로 전환했을까? 에 대해서 짚고 넘어가면서 이번글 마무리 하면 좋을 것 같습니다.

 

비동기로 전환된 API들을 cookies, headers, params, searchParams, draftMode 이와 같습니다.

위 API들은 대부분 개인화와 관련된 요소들입니다.

📌 개인화 데이터란?
사용자별로 매번 달라지는 값들을 말합니다.
예시 :
- cookies: 로그인 토큰, 테마 설정, 언어 설정
- headers: 브라우저 User-Agent, 국가/지역 정보
- params, searchParams: 동적 라우트에서 넘어온 값 (예: /users/123)
- draftMode: 특정 사용자가 프리뷰 모드인지 여부

=> 이런 데이터는 요청이 들어오기 전에는 알 수 없습니다. 따라서 정적 빌드 시점에는 준비가 불가합니다.

 

전통적인 SSR에서는 모든 데이터를 기다린 뒤 페이지 전체를 렌더링합니다.

하지만 이 방식은 개인화가 필요없는 컴포넌트까지 불필요하게 요청을 기다리게 만듭니다ㅠㅠ

  • 예를 들어, 공통 레이아웃이나 정적 콘텐츠는 사실 미리 렌더링해도 문제가 없음.
  • 반면 로그인 사용자 이름, 언어, 세션 정보 같은건 요청이 들어와야함 알 수 있음.

이 두가지를 구분하기 위해 개인화 데이터 관련 API를 전부 비동기화 (Promise) 했습니다.

개발자가 await을 붙이는 순간 "이건 요청 시점에만 알 수 있는 값" 이라고 명확하게 표현하게 되고 Next.js 는 그부분만 동적 처리하게 됩니다.

 

또한 Next.js의 PPR과 함께 생각해볼 수도 있습니다.

RRP ( Partial Prerendering )은 페이지 전체를 동적으로 만드는게 아니라 

  • 정적 렌더링 가능한 부분은 미리 캐싱해두고,
  • 개인화가 필요한 부분만 요청 시점에 렌더링하는 전략입니다.

params, cookies, headers, searchParams를 비동기화한 건, 바로 이 “정적 vs 동적 경계”를 더 명확히 하려는 로드맵의 일부라는 것을 알 수 있습니다.

 

참고 자료
- 공식 문서: https://nextjs.org/docs/messages/next-prerender-sync-params
- 공식 문서: https://nextjs.org/docs/app/building-your-application/upgrading/version-15 
- 블로그: https://reactnext-central.xyz/blog/nextjs/nextjs-15x-versionup-code)

- 공식 문서 : https://nextjs.org/docs/app/getting-started/partial-prerendering

 

 

 

'개발 > Next.js' 카테고리의 다른 글

Next.js 프로젝트에 Drizzle ORM 도입하기 (feat. MySQL)  (1) 2025.11.04
Next.js의 Image 최적화  (0) 2025.10.16