searchParams 관련 서버, 클라이언트 사이드 간 렌더링 오류 해결

·2024년 9월 19일

Trouble Shooting

목록 보기
1/2
post-thumbnail

📋 개념 정의

searchParams와 useSearchParams의 개념 및 차이

둘 다 현재 URL의 query string을 읽을 수 있게 해주는 기능이다. 차이점은 다음과 같다.

searchParams

  • 서버 컴포넌트에서 사용
  • 페이지나 레이아웃 컴포넌트의 props로 직접 전달됨
  • 서버 사이드 렌더링 시 값이 결정되며, 페이지 새로고침 없이는 갱신되지 않음
  • 초기 페이지 로드 시 서버에서 처리되므로 SEO에 유리하고 초기 로딩 성능이 좋음

useSearchParams

  • 클라이언트 컴포넌트에서 사용
  • React 훅으로, 컴포넌트 내에서 호출하여 사용
  • 클라이언트 사이드에서 실시간으로 URL 변경을 감지하고 갱신됨
  • 클라이언트 사이드 라우팅이나 동적 쿼리 파라미터 변경에 적합

🚨 문제

프로젝트를 Vercel로 배포하는 과정에서 로컬로 잘 되던 부분이 오류가 발생했다. 원인을 알아보니 다음과 같았다.

  1. useSearchParams를 서버 사이드 페이지 내 클라이언트 컴포넌트에서 사용
  2. 서버 사이드 렌더링과 클라이언트 사이드 렌더링 사이 불일치로 인한 문제 및 배포 오류

해당 클라이언트 컴포넌트의 코드는 다음과 같았다.

// PaymentComplete.tsx
"use client";
import { useSearchParams } from "next/navigation";

function PaymentComplete() {
  const searchParams = useSearchParams();
  const paymentId = searchParams.get("paymentId");
  // 불필요한 코드 생략
}

🛠️ 해결 과정

공식 문서 및 구글링을 통해 해결 방법을 알아보았다.
공식 문서에서는 Suspense 경계로 (공식문서 넣기)

다음 두 방식 중 고민이 되었다.
Suspense 경계로 감싸는 방식 VS 서버에서 searchParams를 props로 전달하는 방식

장단점 비교

Suspense 경계로 감싸는 방식

장점

  • 코드 구조를 크게 변경하지 않아도 됨
  • 클라이언트 컴포넌트의 로직 그대로 유지 가능

단점

  • 초기 서버 렌더링 시 Suspense 내부의 컴포넌트는 렌더링되지 않으므로 SEO에 영향을 줄 수 있음
  • 클라이언트에서 추가적인 렌더링 필요 (로딩 시간 증가 가능)

서버에서 searchParams를 props로 전달하는 방식

장점

  • 서버 사이드 렌더링을 활용할 수 있어 SEO에 유리
  • 초기 페이지 로드 시 데이터가 포함되어 있어 성능 향상
  • 서버와 클라이언트 간 불일치 문제 원천적으로 해결 가능

단점

  • 클라이언트 컴포넌트의 로직 수정 필요

두 방식의 장단점을 비교했을 때, 사실 클라이언트 컴포넌트의 로직을 바꾸는 것에 큰 부담은 없었다. 때문에 해당 부분을 제외하고 다른 장단점을 놓고 봤을 때 서버에서 searchParams를 props로 전달하는 방식이 훨씬 장점이 많다는 생각이 들어, 해당 방식을 사용하기로 결정했다.

✅ 해결

결과적으로, 일관성 유지 및 불필요한 렌더링 단계 축소, SEO 최적화, 하이드레이션 개선 등의 이점으로 인해 안정적이고 효율적인 후자의 방식을 선택하게 되었다.
이 방식을 통해 배포 오류 및 렌더링 사이 불일치 문제를 해결할 수 있었다.
해결된 코드는 다음과 같다.

// page.tsx (서버 컴포넌트)
function PaymentCompletePage({
  searchParams,
}: {
  searchParams: { paymentId: string };
}) {
  return (
    <Page>
      <PaymentComplete paymentId={searchParams.paymentId as string} />
    </Page>
  );
}

// PaymentComplete.tsx (클라이언트 컴포넌트)
function PaymentComplete({ paymentId }: { paymentId: string }) {
  // 바로 paymentId를 사용할 수 있다.
}
profile
웹 프론트엔드 개발자

0개의 댓글