
평소와 같이 app/[id]/page.tsx 에서 아래와 같이 params를 사용하다 다음과 같은 에러가 발생한 사람이 많을 것 같습니다.
function Page({ params }) {
// direct access of `params.id`.
return <p>ID: {params.id}</p>
}
Error: Route "/posts/[slugAndId]" used params.slugAndId
.
params
should be awaited before using its properties.
Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis
현재 Next.js가 params
를 동기처럼 쓰되 내부적으로 Promise로 감싸도록 설계했기 때문에,TS에서는 이걸 그대로 Promise<{ slugAndId: string }>
로 강제함.
해당페이지의 내용은 다음과 같습니다.
next.js 15버전에서는 아래와 같은 dynamic api들이 비동기적으로 만들어졌다고 한다.
pages, layouts, metadata APIs, and route handlers에 제공되는 params, searchParams props들.
next/headers
에서cookies()
,draftMode()
, andheaders()
즉, params
, searchParams
, cookies
, headers
같은 객체들이 더 이상 즉시 동기적으로 접근할 수 있는 값이 아니고, Promise 형태로 감싸진 비동기 값이 되었음을 의미합니다.
공식문서에서는 다음과 같은 해결책을 제안하고 있습니다.
해결책
1. codemod
로 자동 수정
npx @next/codemod@canary next-async-request-api .
👉 의미:
Next.js에서 바뀐 API들 (
params
,cookies
,headers
등)이 이제 Promise 기반이기 때문에, 이를 자동으로await
또는React.use()
등으로 감싸도록 코드를 변환해줌.해당 codemod가 아래의 2번, 3번을 수행해주는데, 다만 완벽하진 않아서, 일부 코드는 수동으로 고쳐야 함.
2. 서버 컴포넌트에서는 await
사용
async function Page({ params }) {
// asynchronous access of `params.id`.
const { id } = await params
return <p>ID: {id}</p>
}
👉 의미:
params
는 이제 Promise 객체가 될 수 있으므로, 서버 컴포넌트에서는 반드시await
params 해야 함.즉,
params.id
바로 쓰면 안 됨.const { id } = await params
처럼 써야 정상 작동함.마찬가지로
cookies()
,headers()
같은 것도 마찬가지로await
필요.
3. 클라이언트 컴포넌트에서는 React.use()
사용
'use client'
import * as React from 'react'
function Page({ params }) {
// asynchronous access of `params.id`.
const { id } = React.use(params)
return <p>ID: {id}</p>
}
👉 의미:
클라이언트 컴포넌트는
async function
을 사용할 수 없으니, 대신React.use()
로 감싼다.이는
params
가 Promise일 수 있음을 고려한 Suspense 기반의 unwrap 방식임.
4. 마이그레이션 불가 코드 (Unmigratable Cases)
export function MyCookiesComponent() {
const c =
/* @next-codemod-error Manually await this call and refactor the function to be async */
cookies()
return c.get('name')
}
👉 의미:
codemod가 적합한 migrate를 수행하지 못할때, 위의 코드처럼 주석을 남겨놓는다.
예를 들어서,
cookies()
는 Promise인데, 함수가 동기적으로 작성되어 있어서 마이그레이션이 불가능.따라서
MyCookiesComponent
자체를async function
으로 바꾸고await cookies()
를 써야 한다는 경고.codemod가 자동으로 고칠 수 없으므로, 주석이 붙고 빌드 에러가 발생함
5. Linter로 강제 적용
- /* @next-codemod-error <suggested message> */
+ /* @next-codemod-ignore */`
👉 의미:
codemod가 자동 수정 못 한 곳엔
@next-codemod-error
주석이 붙음.이 주석이 남아있으면 빌드할 때 에러 발생.
직접 수정하거나, 정말 무시해도 되는 경우에는
@next-codemod-ignore
로 바꿔야 함.
💡 보너스 팁: "필요할 때만 await"
await params
는 필요할 때까지 지연해서 사용하는 것이 바람직함.그래야 Next.js가 더 많은 부분을 정적으로 렌더링(SSG) 할 수 있음.
예: 헤더나 쿠키가 필요 없는 부분은 먼저 렌더링 가능 → 성능 최적화.
여기까지는 단순히 에러를 없앨 수 있는 방법이다. 근데 대체 왜 dynamic api들을 promise구조로 바꾼 것일까?
결론부터 말하자면 다음과 같습니다.
Next.js가 모든 데이터를 비동기적으로 조합해, 가장 늦게까지 정적으로 만들고, 필요한 시점에만 동적으로 처리하는 렌더링 엔진으로 진화하고 있기 때문이다.
다음 포스트에서는 이와 관련된 내용을 자세하게 다루려고 합니다.