Next.js 15 라우트 params 비동기 처리 중 발생하는 Type Error 해결 방법
Problem
Next.js 15로 업데이트한 후 동적 라우팅(Dynamic Routing) 페이지에서 params 객체에 접근할 때 타입 에러가 발생하는 경우가 많습니다. 특히 params를 비동기적으로 처리하려고 할 때 "Type '{ params: { id: string; }; }' does not satisfy the constraint 'PageProps'"와 같은 빌드 에러를 마주하게 됩니다. 이는 params 객체를 기존 방식처럼 함수의 매개변수에서 바로 구조 분해 할당(Destructuring)하려고 할 때 주로 발생하며, 타입스크립트가 Promise 객체에서 필요한 속성을 찾지 못해 빌드가 실패하는 원인이 됩니다.
Background
Next.js 15부터는 동적 API(Dynamic APIs)의 처리 방식에 큰 변화가 생겼습니다. 기존에는 동적 라우트의 params나 searchParams를 동기적인 객체로 바로 사용할 수 있었지만, Next.js 15부터는 성능 최적화와 렌더링 모델 개선을 위해 이들이 비동기(Asynchronous) 방식으로 변경되었습니다. 즉, params는 이제 단순한 객체가 아니라 Promise를 반환합니다. 따라서 기존처럼 컴포넌트의 매개변수 부분에서 { params } 형태로 바로 구조 분해 할당을 시도하면, 타입스크립트는 이를 Promise가 아닌 일반 객체로 인식하여 타입 불일치 에러를 발생시키는 것입니다. 이 변경 사항을 이해하고 Promise를 올바르게 언래핑(Unwrapping)하는 것이 문제 해결의 핵심입니다.
Solution
이 문제를 해결하려면 매개변수에서 직접 구조 분해 할당을 하지 않고, props 객체 전체를 받은 뒤 컴포넌트 내부에서 비동기 처리를 해야 합니다. 컴포넌트의 종류(서버/클라이언트)에 따라 두 가지 방법으로 해결할 수 있습니다.
1. 서버 컴포넌트 (async/await 사용)
서버 컴포넌트에서는 async 함수 내에서 await 키워드를 사용하여 params Promise를 해결할 수 있습니다.
// params의 타입을 Promise로 정의합니다.
type Params = Promise<{ slug: string[] }>;
// 매개변수에서 { params }로 바로 구조 분해 할당하지 않고 props 전체를 받습니다.
export default async function Challenge(props: { params: Params }) {
// 컴포넌트 내부에서 props.params를 await로 대기한 후 구조 분해 할당을 수행합니다.
const { slug } = await props.params;
const productID = slug[1];
return (
<div>
<h1>Product ID: {productID}</h1>
</div>
);
}
이 방식은 직관적이며 서버 컴포넌트에서 데이터를 패칭하는 기존의 async/await 패턴과 잘 어울립니다.
2. 클라이언트 컴포넌트 또는 동기 함수 (React use 훅 사용)
클라이언트 컴포넌트("use client")이거나 async 키워드를 사용할 수 없는 상황이라면, React 19에서 도입된 use 훅을 사용하여 Promise를 처리해야 합니다.
"use client";
import { use } from "react";
type Params = Promise<{ id: string }>;
// async 키워드가 없는 일반 컴포넌트
export default function CategoryDetail(props: { params: Params }) {
// React의 use() 훅을 사용하여 Promise의 값을 읽어옵니다.
const { id } = use(props.params);
return (
<div>
<h1>Category ID: {id}</h1>
</div>
);
}
use 훅은 컴포넌트 렌더링 중에 Promise가 해결될 때까지 기다리게 해주며, async/await를 사용할 수 없는 환경에서 완벽한 대안이 됩니다.
Deep Dive
Next.js 14에서 15로 마이그레이션하는 경우, params뿐만 아니라 searchParams 역시 동일하게 비동기 Promise로 처리해야 함을 잊지 말아야 합니다. 또한, await props.params를 호출할 때 렌더링이 블로킹되므로, 필요한 데이터 패칭 로직과 params 언래핑 로직을 어떻게 병렬로 처리할지 고민하면 성능 향상에 도움이 됩니다. React의 use 훅을 사용할 때는 조건문이나 반복문 내부가 아닌 컴포넌트의 최상단 레벨에서 호출해야 한다는 React 훅의 기본 규칙을 반드시 준수해야 합니다.
Conclusion
Next.js 15에서 발생하는 params 타입 에러는 동적 라우팅 파라미터가 비동기 Promise로 변경되면서 발생하는 자연스러운 현상입니다. 함수의 매개변수에서 직접 구조 분해 할당을 피하고, 서버 컴포넌트에서는 await를, 클라이언트 컴포넌트에서는 React의 use() 훅을 사용하여 props.params를 처리함으로써 이 문제를 깔끔하게 해결할 수 있습니다.