Web Devlopment/NextJs

#13. params, searchParams (15+ 버전 에서 지켜야 할 점)

키모형 2025. 12. 26. 11:05
반응형

Next.js App Router에서 페이지/레이아웃 컴포넌트는 자동으로 params, searchParams를 받을 수 있어요.

둘 다 “라우팅에서 온 값”이지만 성격이 완전히 다릅니다.

 

1) params = 경로(Path) 파라미터

폴더 이름에 있는 동적 세그먼트가 params로 들어옵니다.

예시

파일: app/movies/[id]/page.tsx

URL: /movies/123

코드:

export default function Page({
  params,
}: {
  params: { id: string };
}) {
  return <div>Movie ID: {params.id}</div>; // "123"
}

특징

  • 라우트 구조로 결정됨 ([id], [...slug], [[...slug]])
  • 보통 “리소스 식별자” (id, slug 등)
  • 값은 문자열(또는 catch-all이면 string[])

catch-all 예시

app/blog/[...slug]/page.tsx
/blog/2025/12/26 → params.slug = ["2025","12","26"]

 


2) searchParams = 쿼리스트링(Query String)

URL의 ?key=value 부분이 searchParams로 들어옵니다.

예시

URL: /invoices?page=2&query=kim&status=paid

코드:

export default function Page({
  searchParams,
}: {
  searchParams: { page?: string; query?: string; status?: string };
}) {
  return (
    <div>
      page={searchParams.page}, query={searchParams.query}, status={searchParams.status}
    </div>
  );
}

특징

  • “필터/정렬/페이지네이션”에 주로 사용
  • 값은 기본적으로 string | undefined
  • 같은 키가 여러 번 나오면(예: ?tag=a&tag=b) 처리 방식이 케이스가 갈리므로 보통 직접 파싱/정규화합니다.

3) 언제 뭘 쓰면 좋은가 (실전 기준)

  • params: “페이지 자체가 달라지는 기준”
    • 상세 페이지 id (/customers/42)
    • 카테고리 slug (/products/shoes)
  • searchParams: “같은 페이지에서 목록 조건만 바뀌는 기준”
    • 검색어, 상태 필터, 정렬, 페이지번호
    • 예: /invoices?query=abc&page=3&status=pending

4) App Router에서 중요한 점 2가지

  1. Server Component에서 기본으로 사용 가능
    page.tsx는 기본이 서버 컴포넌트라 DB 조회에 바로 활용 가능.
  2. 값 타입은 문자열이라서 숫자/불리언은 직접 변환해야 안전합니다.

 

 

Next.js 15부터(App Router) page.tsx(그리고 layout, generateMetadata 등)에 주입되는 params, searchParams가 “Promise”로 바뀌었어요. 그래서 값을 쓰려면 **await(또는 React의 use()로 Promise 해제)**를 하는 방식이 “정석”이 됐습니다. Next.js+1

버전별 핵심 차이

  • Next 14 이하: params, searchParams가 동기 객체
  • Next 15+: params, searchParams가 Promise
    • 하위호환으로 동기 접근이 아직은 가능하지만(Next 15), 향후 deprecated 예정 Next.js
    • 그리고 특정 설정(예: cacheComponents)에서는 동기 접근이 에러가 될 수 있음 Next.js

추천 타입/코드 

export default async function DashBoardDetail({
  params,
  searchParams,
}: {
  params: Promise<{ id: string }>;
  searchParams: Promise<{ page?: string; query?: string; status?: string | string[] }>;
}) {
  const { id } = await params;
  const { page = "1", query = "", status } = await searchParams;

  const pageNo = Number(Array.isArray(page) ? page[0] : page) || 1;

  return (
    <main>
      DashBoardDetail Page [{id}]
      <p>page = {pageNo} / query = {query} / status = {String(status ?? "")}</p>
    </main>
  );
}

“async/await 강제냐?”

  • 값을 읽는다면 사실상 강제예요. params/searchParams가 Promise라서요. Next.js
  • 다만 페이지가 Client Component('use client')라서 async를 못 쓰는 경우에는,
import { use } from "react";
const { id } = use(params);
const sp = use(searchParams);

 

처럼 use()로 Promise를 해제할 수 있어요.

 

1) use()가 하는 일 (핵심)

✅ Promise를 use(promise)로 읽기

  • use(promise)를 호출하면, Promise가 resolve될 때까지 해당 컴포넌트 렌더링이 잠시 멈추고(Suspense)
  • resolve되면 값이 반환됩니다.
  • reject되면 가장 가까운 Error Boundary로 전달됩니다. ko.react.dev

✅ Context를 use(SomeContext)로 읽기

  • use(Context)는 useContext(Context)와 비슷하게 동작합니다.
  • 차이점: useContext는 컴포넌트 최상단에서만 호출해야 하는 반면, use는 조건문/반복문 내부에서도 호출 가능하다고 문서에 명시돼 있어요. ko.react.dev

2) Next.js(App Router)에서 use()가 등장한 이유

Next.js 15+에서 page.tsx의 params, searchParams가 Promise로 주입됩니다.
그래서 값을 꺼낼 때 **await 또는 React의 use()**가 필요해요. Next.js


3) Server Component vs Client Component에서 권장 사용

Server Component(기본 page.tsx)

서버 컴포넌트에서는 async/await를 권장합니다. (React 문서도 서버에서 데이터는 await 선호)

 

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  return <div>{id}</div>;
}

Client Component('use client')

클라 컴포넌트는 보통 async 컴포넌트를 못 쓰는 경우가 많아서,

  • (A) 라우트 값 읽기: Next 제공 훅 사용 (useParams, useSearchParams) Next.js+1
  • (B) “Promise props”를 넘겨받아 풀어야 한다: React use(promise) 사용 (Suspense/에러바운더리 필요) ko.react.dev+1

A 예시(라우터에서 직접 읽기):

'use client'
import { useParams, useSearchParams } from 'next/navigation'

export default function Client() {
  const { id } = useParams<{ id: string }>()
  const sp = useSearchParams()
  const page = sp.get('page')
  return <div>{id} / page={page}</div>
}

 

B 예시( Promise props”를 넘겨받아 풀어야 한다) :

"use client";
import * as React from "react";

export default function DetailClient({
  params,
  searchParams,
}: {
  params: Promise<{ id: string }>;
  searchParams: Promise<{ page?: string; query?: string; status?: string }>;
}) {
  const { id } = React.use(params);
  const { page, query, status } = React.use(searchParams);

  return (
    <section>
      <div>id: {id}</div>
      <div>page: {page}</div>
      <div>query: {query}</div>
      <div>status: {status}</div>
    </section>
  );
}

 

4) 주의할 점 2개

  1. use(promise)는 **렌더링 중에 멈춤(Suspense)**을 만들기 때문에, 보통 <Suspense fallback=...> 경계가 필요합니다. ko.react.dev
  2. Next에서 searchParams는 “Dynamic API”라서 사용하면 페이지가 요청 시점 동적 렌더링으로 전환될 수 있어요. Next.js

아래와 같은 규칙을 지키도록 하자!!!

  • “서버 컴포넌트에서는 await로 통일”
  • “클라 컴포넌트에서는 useParams/useSearchParams로 통일”
    이렇게 프로젝트 규칙을 하나로 정리해서 코딩을 하도록 합시다

 

반응형