들어가며: 레이아웃 유지의 중요성

Next.js는 페이지 이동 시 공통 레이아웃을 유지하면서 성능을 최적화한다. 하지만 이러한 최적화 과정에서 쿼리 파라미터나 경로 정보를 올바르게 관리하는 것이 중요하다.

쿼리 파라미터 관리하기

1. searchParams prop 사용

// app/teams/page.tsx
export default function TeamsPage({
  searchParams,
}: {
  searchParams: { filter: string; sort: string }
}) {
  return (
    <div>
      <h1>팀 목록</h1>
      <p>필터: {searchParams.filter}</p>
      <p>정렬: {searchParams.sort}</p>
    </div>
  );
}

2. useSearchParams 훅 활용

// components/TeamFilter.tsx
'use client';
import { useSearchParams } from 'next/navigation';

export default function TeamFilter() {
  const searchParams = useSearchParams();
  const filter = searchParams.get('filter');

  return (
    <div>
      <h2>현재 필터</h2>
      <div className="filter-status">
        {filter === 'active' ? '활성 팀만 보기' : '모든 팀 보기'}
      </div>
    </div>
  );
}

경로 정보 다루기

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

// app/products/[category]/page.tsx
export default function CategoryPage({
  params,
}: {
  params: { category: string }
}) {
  return (
    <div>
      <h1>{params.category} 카테고리</h1>
      <ProductList category={params.category} />
    </div>
  );
}

2. usePathname 사용 (클라이언트 컴포넌트)

// components/Navigation.tsx
'use client';
import { usePathname } from 'next/navigation';

export default function Navigation() {
  const pathname = usePathname();

  return (
    <nav>
      <Link
        href="/"
        className={pathname === '/' ? 'active' : ''}
      >
        홈
      </Link>
      <Link
        href="/products"
        className={pathname.startsWith('/products') ? 'active' : ''}
      >
        제품
      </Link>
    </nav>
  );
}

실제 구현 사례

1. 필터링과 정렬이 있는 제품 목록

// app/products/page.tsx
import { ProductFilter } from '@/components/ProductFilter';
import { ProductSort } from '@/components/ProductSort';
import { ProductList } from '@/components/ProductList';

export default function ProductsPage({
  searchParams,
}: {
  searchParams: {
    category?: string;
    sort?: string;
    page?: string;
  }
}) {
  return (
    <div className="products-page">
      <div className="filters">
        <ProductFilter />
        <ProductSort />
      </div>
      <ProductList
        category={searchParams.category}
        sort={searchParams.sort}
        page={searchParams.page}
      />
    </div>
  );
}

2. 동적 필터 컴포넌트

// components/ProductFilter.tsx
'use client';
import { useSearchParams, useRouter } from 'next/navigation';

export default function ProductFilter() {
  const router = useRouter();
  const searchParams = useSearchParams();

  const updateFilter = (category: string) => {
    const params = new URLSearchParams(searchParams.toString());
    params.set('category', category);
    router.push(`/products?${params.toString()}`);
  };

  return (
    <div className="filter-controls">
      <button onClick={() => updateFilter('electronics')}>
        전자기기
      </button>
      <button onClick={() => updateFilter('clothing')}>
        의류
      </button>
    </div>
  );
}

주의사항과 모범 사례

1. 레이아웃에서의 쿼리 파라미터 접근

// ❌ 잘못된 사용
// app/layout.tsx
export default function Layout({ children }) {
  // 레이아웃에서 직접 쿼리 파라미터 접근 불가
  const searchParams = useSearchParams(); // 에러 발생!
  return <div>{children}</div>;
}

// ✅ 올바른 사용
// 필요한 정보를 자식 컴포넌트로 전달
export default function Layout({ children }) {
  return (
    <div>
      {children}
    </div>
  );
}