본문 바로가기
카테고리 없음

Search Params 잘 쓰고 있었는데, Next 15에서는 왜 타입이 Promise 일까?

by klm hyeon woo 2025. 5. 10.

목차

· SearchParams의 타입 에러

· 잘 쓰고있었는데 왜 바뀐걸까?

   · React use()의 등장

· 변경된 요소로 인한 주의사항

· 레퍼런스


SearchParams의 타입 에러

최근에 주변 취업을 준비하는 친구들이 자주보던 서비스가 사라져서 직접 만들던 도중에, Next의 Page Router만 많이 사용을 해봤던 경험을 뒤로 App Router로 프로젝트를 만드는 도전을 해봤어요. Next 버전이 생각보다 많이 올라서 라우팅 방식이 어떻게 달라졌을지 경험도 해볼겸(?) 현재 제작 중인 서비스는 다른 사람들에게 정보 공유도 가능하기에 URL에 여러 category, company 명과 같은 부가 값들을 제어하며 진행해야하기에 SSR을 택하게 되었고, 그래서 URL에 담긴 값들을 활용할 일들이 되게 많았답니다. 이를테면 공유를 하는 사람이 카테고리나 관련 정보들을 설정하고 URL 통째로 공유를 하면 공유 받은 사람도 그 필터 그대로 유지를 시켜줘야하기에 사용을 하게 되었고, 물론 CSR에서도 충분히 구현이 가능하지만 동적 메타태그도 같이 전달을 해주고 싶었어요.

 

그러다보니 자연스럽게 서버 사이드 환경에서 path에서 추출해 사용하는 searchParams를 많이 사용하게 되었는데, 로컬 환경에서는 잘 되는데 계속 실패하는 빌드.. 빌드 로그를 한번 보았는데, 아래 메세지가 출력이 되었어요.

Type error: Type 'paramsType' does not satisfy the constraint 'PageProps'.
  Types of property 'searchParams' are incompatible.
    Type '{ company: string; category: string; }' is missing the following properties from type 'Promise<any>': then, catch, finally, [Symbol.toStringTag]

 

P.. Promise..? 왜 갑자기 이런 에러가 발생할까에 대해 관련해서 공식 문서나, 자료들을 확인하니 Next.js의 버전 업데이트에 의해 searchParams가 비동기 처리 방식으로 전환이 되었다고 해요. 회사 경험으로 인해 이럴 때는 마이그레이션 문서를 보는게 빠르거나, 아니면 포럼에서 이런 주제로 토론을 많이하기에 관련 주제를 빠르게 캐치할 수 있었어요. 기존 Page에서 searchParams를 가져오는 방식은 아래와 같았어요. searchParams가 지칭하고 있는 타입을 보면 동기적으로 타입이 명시되어있을 것을 확인할 수 있는데, 변경된 코드를 보면 전혀 달라졌어요.

'use client'
 
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
 
export default function Page({
  params,
  searchParams,
}: {
  params: Params
  searchParams: SearchParams
}) {
  const { slug } = params
  const { query } = searchParams
}

공식 문서에서 Next15 마이그레이션 가이드에서는 아래 코드처럼 searchParams의 타입을 Promise로 명시를 하고 있고, 또 관련하여 use라는 새로운 훅을 제시하고 있었어요. React의 새로운 버젼에서 나온 개념같은데, 사실 use라는 훅을 이번 기회에 처음 보게 되었답니다.

// After
import { use } from 'react'
 
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
 
export default function Page(props: {
  params: Params
  searchParams: SearchParams
}) {
  const params = use(props.params)
  const searchParams = use(props.searchParams)
  const slug = params.slug
  const query = searchParams.query
}

잘 쓰고있었는데 왜 바뀐걸까?

기존 Next 13 · 14에서 사용하고 있던 스타일은 params와 searchParams가 동기 값으로 컴포넌트로 내려오는 구조였어요.

type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }

그런데 Next 15에서는 params와 searchParams는 Promise로 감싸진 값으로 내려오는 구조로 변경이 되었어요.

이거 때문에 빌드 에러가 나서 Vercel에 배포 문제를 30분 동안 잡고 있었던건 비밀

type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>

React use()의 등장

잠시 번외로 React로 주제를 돌려 이야기를 하자면, React 19에서 use() 훅의 존재를 알게 되었어요. React 19에서는 컴포넌트 안에서 Promise를 다루는 새로운 방법인 use()훅을 도입했어요. use() 훅은 Promise가 resolve 될 때까지 서버에서 렌더링을 "멈추고 기다렸다가" 값을 가져오게 해줘요. 즉, 컴포넌트 안에서 await 없이 promise를 안전하게 사용할 수 있게 되는거죠.

const user = use(fetchUser())

 

Next15에서는 React 19를 채택하면서, generateMetadata나 page 같은 서버 컴포넌트에서 props로도 async 값을 내려줄 수 있게 만들었어요. params, searchParams를 이제 promies로 내려받을 수 있고, 컴포넌트에서는 이를 use()로 처리할 수 있게된 셈이에요. 이렇게 하면서 Next15에서는 서버 컴포넌트에서 점진적 렌더링(streaming)이나 async props 패턴을 일관되게 사용하는 것과 더불어 아래의 이점들을 가져올 수 있게된거예요.

async props 패턴을 일관되게 사용한다는 말이 무슨 말이에요?

· 기존 React 18까지의 방식
이전에는 서버 컴포넌트에서 비동기 데이터를 사용하려면 상위에서 await 처리 후에 하위 컴포넌트로 props를 내려주는 방식을 채택을 했어요
export default async function Page() {
  const user = await getUser()
  return <Profile user={user} />
}​

 

· React 19 + use()를 활용한 방식
컴포넌트 안에서 Promise를 use()로 직접 받을 수 있게 되었어요. 이로 인해 코드 분리가 더 쉽고, async/await 컴포넌트 트리로 래핑 할 필요가 없으며 React 서버 렌더러가 Suspense를 결합하여 비동기 처리 + streaming 렌더링을 더 잘할 수 있게 되었어요.
export default function Page() {
  const user = use(getUser())
  return <Profile user={user} />
}​
  • 코드 분리
    • 과거 : 최상위에서만 비동기 처리가 가능했으며 이로 인해 계층 구조가 깨짐
    • 현재 : 컴포넌트 어디서나 비동기 처리가 가능해 설계가 유연해짐
  • 점진적 스트리밍 기법
    • Next는 React의 "streaming renderer"로 페이지를 점진적으로 내려보내줄 수 있음
    • use()로 각 컴포넌트의 데이터가 준비될 때마다 렌더링을 진행할 수 있음 (사용성 개선)
  • Suspense 통합
    • Suspense fallback이랑 연결되어 비동기 로딩 처리 UX를 조금 더 편리하게 할 수 있음

이러한 이점들로 인해 비동기 처리, 코드 분리, 성능(스트리밍을 이용한)까지 모두 개선하기 위해, Next15에서는 props를 Promise로 바꾸게 된거예요. 단 이로인해 searchParams와 같은 props에 Promise를 붙이는 것까지 이해를 했지만 주의사항은 따로 없을까요?

변경된 요소로 인한 주의 사항

· use()는 서버 컴포넌트 전용이에요, 클라이언트 컴포넌트에서 사용을 하게 되면 에러가 발생해요.

· 타입 정의는 저처럼 삽질을 하지 않기 위해서는 Promise 타입을 명확히 해야 빌드 에러가 발생하지 않아요.

· 기존 동기 컴포넌트를 마이그레이션 하게 되면, params/searchParams와 같은 타입을 점검을 해보세요 (Promise 타입 적용)


레퍼런스

 

 

리액트의 신규 훅, "use"

https://yceffort.kr

yceffort.kr

 

Upgrading: Version 15 | Next.js

Upgrade your Next.js Application from Version 14 to 15.

nextjs.org

 

PageProps Type Errors in Next.js · community · Discussion #142577

Select Topic Area Bug Body Hello everyone, I am working on a project using Next.js 15 and have defined my page as follows: export default async function Page({ params }: { params: { slug: string[] ...

github.com

댓글