정리

searchParams vs useSearchParams

특징 searchParams useSearchParams
사용 환경 서버 컴포넌트 클라이언트 컴포넌트
선언 방식 props로 자동 전달 React Hook으로 import
데이터 형태 일반 객체 URLSearchParams 객체
사용 방법 searchParams.category searchParams.get('category')
변경 감지 페이지 새로고침 실시간 (자동 갱신)
주 사용 목적 초기 데이터 로딩, SEO 동적 필터링, 실시간 업데이트

params vs usePathname

특징 params usePathname
사용 환경 서버 컴포넌트 클라이언트 컴포넌트
선언 방식 props로 자동 전달 React Hook으로 import
반환 값 동적 라우트 파라미터 객체 전체 경로 문자열
사용 예시 params.slug pathname (전체 경로)
주요 용도 동적 라우팅 처리 현재 경로 기반 UI 업데이트
데이터 범위 동적 세그먼트만 포함 전체 URL 경로 반환

‘use client’: event listener와 hook을 사용할 수 있는 component

Params 접근 방법

1. searchParams prop 사용 - (Server Component)

: 서버에서 이미 전달받은 Parameter를 사용합니다.

// app/invoices/page.tsx
export default async function Page(props: {
  searchParams?: Promise<{ query?: string; page?: string }>;
}) {
  const searchParams = await props.searchParams;
  const query = searchParams?.query || "";
  const currentPage = Number(searchParams?.page) || 1;

  return (
    <div className="w-full">
      <div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
        <Search placeholder="Search invoices..." />
        <CreateInvoice />
      </div>
      <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
        <Table query={query} currentPage={currentPage}
      </Suspense>
      <div className="mt-5 flex w-full justify-center">
        {/* <Pagination totalPages={totalPages} /> */}
      </div>
    </div>
  );
}

// ui/table.tsx - Server Component : 전달받은 params 사용
export default async function InvoicesTable({
  query,
  currentPage,
}: {
  query: string;
  currentPage: number;
}) {
  const invoices = await fetchFilteredInvoices(query, currentPage);
  // ...
}

2. useSearchParams Hook 활용 (클라이언트 컴포넌트)

: 브라우저에서 직접 URL Parameter를 읽습니다.

// ui/search.tsx
"use client";

import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useDebouncedCallback } from "use-debounce";

// ex) URL: /dashboard/invoices?page=1&query=pending
export default function Search({ placeholder }: { placeholder: string }) {
  const searchParams = useSearchParams(); // {page: '1', query: 'pending'}
  const pathname = usePathname(); // '/dashboard/invoices'
  const { replace } = useRouter(); // 클라이언트 컴포넌트에서 프로그래밍 방식으로 라우팅

  const handleSearch = useDebouncedCallback((term: string) => {
    const params = new URLSearchParams(searchParams);

    if (term) params.set("query", term);
    else params.delete("query");

    replace(`${pathname}?${params.toString()}`);
  }, 300);

  return (
    <div className="relative flex flex-1 flex-shrink-0">
      <label htmlFor="search" className="sr-only">
        Search
      </label>
      <input
        defaultValue={searchParams.get("query")?.toString()}
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        onChange={(e) => handleSearch(e.target.value)}
      />
    </div>
  );
}

경로 정보 다루기

1. params 사용 (서버 컴포넌트)