이 글에서 정리하는 내용
App Router에서 useSearchParams를 쓴 클라이언트 컴포넌트 때문에 production build가 실패하는 상황을 기준으로 원인을 좁히고, 실제 프로젝트에서 어떤 설정과 코드 구조를 확인해야 하는지 정리합니다. 단순 개념 소개가 아니라 오류가 난 순간 바로 확인할 순서와 선택 기준을 중심으로 설명합니다.
이 글은 Next.js SEO 완전 가이드: App Router metadata부터 배포 확인까지의 세부 항목입니다. 전체 설정 흐름과 관련 오류 해결 순서는 대표 허브 글에서 함께 확인할 수 있습니다.
- useSearchParams 오류가 빌드에서 터지는 이유
- App Router에서 클라이언트 훅을 쓰는 위치
- Suspense로 감싸야 하는 컴포넌트 기준
- 검색·필터 UI에서 안전한 구조
- Vercel 배포 전 체크리스트
useSearchParams 오류가 빌드에서 터지는 이유

App Router의 정적 페이지에서 클라이언트 컴포넌트가 useSearchParams를 사용하면 production build에서 Suspense 경계가 필요할 수 있습니다. 개발 서버에서는 on-demand로 렌더링되어 괜찮아 보이다가, next build에서만 Missing Suspense boundary with useSearchParams 오류가 드러나는 패턴이 흔합니다.
먼저 useSearchParams가 어느 컴포넌트에서 호출되는지 찾습니다. 직접 작성한 검색 바뿐 아니라 progress bar, analytics provider, layout 하위 client component가 내부에서 호출하는 경우도 있습니다. 배포 흐름까지 같이 확인하려면 프론트엔드 배포 로드맵도 함께 보면 좋습니다.
App Router에서 클라이언트 훅을 쓰는 위치
useSearchParams는 Client Component에서 현재 URL의 query string을 읽는 hook입니다. Server Component 페이지에서 검색 조건으로 데이터를 가져와야 한다면 page의 searchParams prop을 먼저 쓰고, 상호작용이 필요한 작은 영역만 client component로 분리하는 편이 단순합니다.
검색 UI처럼 URL query를 읽기만 하는 부분은 client component로 작게 분리하고, 그 컴포넌트를 가장 가까운 Suspense로 감싸는 기준이 좋습니다. 페이지 전체를 무작정 client component로 바꾸면 정적 렌더링과 서버 데이터 조회의 장점을 잃기 쉽습니다.
import { Suspense } from "react";
export default function Page() {
return (
<Suspense fallback={<div>검색 조건을 불러오는 중</div>}>
<FilterBar />
</Suspense>
);
}
이 구조에서 FilterBar가 useSearchParams를 호출하더라도 Suspense 경계 안에 들어갑니다. 중요한 것은 hook을 쓰는 컴포넌트를 감싸는 것이지, 오류가 난 page 파일 전체를 무조건 client component로 바꾸는 것이 아닙니다.
만약 route 자체를 요청 시점에 동적으로 렌더링해야 한다면 Server Component에서 connection()을 사용하는 방식도 검토할 수 있습니다. 다만 단순 검색 바 문제라면 먼저 작은 client component와 Suspense 경계로 해결되는지 확인하는 것이 변경 범위가 작습니다.
Suspense로 감싸야 하는 컴포넌트 기준
Suspense로 감싸야 하는 대상은 useSearchParams를 직접 호출하거나 그 hook을 내부에서 쓰는 client component입니다. 검색 바, 필터 패널, 페이지네이션 컴포넌트처럼 query string을 읽는 작은 UI를 분리하면 fallback 범위도 작게 유지됩니다.
오류가 dev에서는 보이지 않고 build에서만 보이면 정상적인 단서입니다. 수정 후에는 npm run build로 다시 확인하고, 캐시나 데이터 갱신 문제와 섞이면 Next.js fetch 캐시 문제 해결처럼 렌더링 기준을 따로 분리해 봅니다.
검색·필터 UI에서 안전한 구조

검색·필터 UI에서는 page가 searchParams prop으로 초기 조건을 읽고, client component는 사용자가 값을 바꿀 때 URL을 갱신하는 역할로 나누면 구조가 단순합니다. 이렇게 하면 서버 데이터 조회와 클라이언트 상호작용의 책임이 섞이지 않습니다.
URL query를 읽는 컴포넌트가 여러 곳에 흩어져 있다면 먼저 하나의 FilterBar 같은 작은 단위로 모읍니다. layout 전체나 provider 전체가 useSearchParams에 의존하면 Suspense 범위가 커지고, 빌드 오류 위치도 추적하기 어려워집니다.
Vercel 배포 전 체크리스트
마지막으로 체크리스트를 남겨두면 다음에 같은 증상을 만났을 때 훨씬 빠르게 움직일 수 있습니다. 첫째, 오류 메시지를 그대로 검색하기 전에 실행 환경을 확인합니다. 둘째, 설정 파일과 코드가 같은 기준을 바라보는지 봅니다. 셋째, 수정 후에는 로컬 실행, production build, 필요한 경우 배포 환경까지 나누어 검증합니다.
체크리스트를 만들 때는 “무엇을 바꿨는가”보다 “무엇을 확인했는가”를 먼저 적는 편이 좋습니다. 예를 들어 파일 위치를 확인했는지, 빌드 명령으로 재현했는지, 배포 환경에서도 같은 값이 들어가는지, 변경 후 다시 실패 조건을 실행했는지처럼 검증 행동을 남깁니다. 이런 기록이 있으면 다음에 비슷한 문제가 생겼을 때 해결 과정이 훨씬 짧아집니다.
가능하면 수정 전후를 한 줄로 기록해 두세요. 예를 들어 “useSearchParams 호출 컴포넌트를 FilterBar로 분리”, “FilterBar를 가장 가까운 Suspense로 감쌈”, “next build에서 같은 오류가 사라지는지 확인”처럼 남기면 됩니다. 이런 기록은 글을 읽는 독자에게도 그대로 적용 가능한 문제 해결 패턴이 됩니다.
반대로 피해야 할 방식은 오류가 사라질 때까지 설정을 무작위로 바꾸는 것입니다. 당장은 통과하더라도 왜 해결됐는지 모르면 다음 배포나 협업 환경에서 같은 문제가 다시 생깁니다. 작은 단위로 가설을 세우고 하나씩 확인하는 방식이 느려 보여도, 결과적으로는 가장 빠른 해결에 가깝습니다.
마지막으로, 해결이 끝났다고 판단하기 전에는 같은 증상을 일부러 한 번 더 재현해 보는 것이 좋습니다. 실패 조건을 다시 만들 수 있고, 수정 후 그 조건이 사라졌다면 해결 근거가 생깁니다. 이 확인 없이 화면이 한 번 정상으로 보였다는 이유만으로 끝내면, 실제 배포나 다른 브라우저에서 문제가 다시 나타날 수 있습니다.
이 글의 핵심은 특정 옵션 하나를 외우는 것이 아닙니다. 오류가 났을 때 어디서부터 확인해야 하는지 순서를 갖는 것입니다. 그 순서가 있으면 비슷한 문제가 다시 나와도 무작정 코드를 바꾸지 않고, 원인을 작게 나누어 안정적으로 해결할 수 있습니다.