특징 | searchParams | useSearchParams |
---|---|---|
사용 환경 | 서버 컴포넌트 | 클라이언트 컴포넌트 |
선언 방식 | props로 자동 전달 | React Hook으로 import |
데이터 형태 | 일반 객체 | URLSearchParams 객체 |
사용 방법 | searchParams.category |
searchParams.get('category') |
변경 감지 | 페이지 새로고침 시 | 실시간 (자동 갱신) |
주 사용 목적 | 초기 데이터 로딩, SEO | 동적 필터링, 실시간 업데이트 |
특징 | params | usePathname |
---|---|---|
사용 환경 | 서버 컴포넌트 | 클라이언트 컴포넌트 |
선언 방식 | props로 자동 전달 | React Hook으로 import |
반환 값 | 동적 라우트 파라미터 객체 | 전체 경로 문자열 |
사용 예시 | params.slug |
pathname (전체 경로) |
주요 용도 | 동적 라우팅 처리 | 현재 경로 기반 UI 업데이트 |
데이터 범위 | 동적 세그먼트만 포함 | 전체 URL 경로 반환 |
‘use client’: event listener와 hook을 사용할 수 있는 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);
// ...
}
<Search>
is a Client Component, so you used the useSearchParams()
hook to access the params from the client.<Table>
is a Server Component that fetches its own data, so you can pass the searchParams
prop from the page to the component.: 브라우저에서 직접 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>
);
}