2026. 7. 7.
Next.js 빌드가 useSearchParams에서 멈출 때 3분 Suspense 수정
Next.js App Router에서 useSearchParams를 넣은 뒤 next build가 Suspense 오류로 멈출 때, 전체 페이지를 다시 만들지 않고 작은 컴포넌트 분리와 Suspense 감싸기로 확인하는 3분 수정 순서입니다.

로컬에서는 검색 페이지가 잘 열렸는데 npm run build나 Vercel 배포에서 갑자기 멈추는 경우가 있습니다. 오류에 useSearchParams()와 Suspense boundary가 같이 보이면, 처음부터 페이지를 다시 만들 필요가 없습니다. 대부분은 검색어를 읽는 작은 클라이언트 컴포넌트를 분리하고 그 부분만 <Suspense>로 감싸면 빌드가 다시 통과합니다.
오늘 목표는 React 전체 구조 공부가 아닙니다. 빌드가 멈춘 위치를 찾고, 검색 파라미터를 읽는 부분만 안전하게 감싸는 것입니다. AI에게 바로 "전체 코드 고쳐줘"라고 보내기 전에 아래 순서대로 3분만 확인합니다.
nextjs.org의 공식 문서는 useSearchParams를 Client Component hook으로 설명합니다. 또 정적으로 프리렌더링되는 페이지에서 Client Component가 useSearchParams를 호출하면, 프로덕션 빌드에서는 가까운 Suspense 경계가 필요하다고 안내합니다. 개발 서버에서는 온디맨드로 렌더링되어 멀쩡해 보일 수 있으므로, 이 오류는 로컬 화면보다 빌드 로그를 기준으로 봐야 합니다.
30초 안에 오류 줄과 훅 위치를 분리합니다
먼저 터미널에서 처음 보이는 핵심 줄을 찾습니다. 보통 이런 식입니다.
Error: useSearchParams() should be wrapped in a suspense boundary
at SearchPage (app/search/page.tsx:5:24)
여기서 볼 것은 두 가지입니다. 하나는 파일 경로이고, 다른 하나는 useSearchParams()가 실제로 호출되는 컴포넌트입니다. app/search/page.tsx 전체가 문제라는 뜻으로 받아들이면 수정 범위가 너무 커집니다. 문제의 중심은 useSearchParams()를 부른 컴포넌트가 Suspense 경계 밖에 있다는 점입니다.
AI에게 물을 때도 이 위치를 먼저 줍니다.
Next.js App Router에서 npm run build가 실패합니다.
오류는 app/search/page.tsx 5번째 줄의 useSearchParams()입니다.
전체 페이지를 다시 만들지 말고, 이 훅을 쓰는 가장 작은 컴포넌트를 분리한 뒤 Suspense로 감싸는 최소 수정만 제안해 주세요.
오류 위치가 분명하지 않으면 Next.js 공식 오류 문서가 안내하는 next build --debug-prerender를 한 번 실행해 봅니다. 이 명령은 프리렌더 단계에서 어떤 컴포넌트가 문제인지 찾는 데 도움이 됩니다.
1분 안에 검색 파라미터를 읽는 부분만 Client Component로 뺍니다
초보자가 가장 자주 하는 실수는 page.tsx 전체 위에 'use client'를 붙이고 그 안에서 useSearchParams()를 바로 호출하는 것입니다. 빌드 오류를 줄이려면 페이지 전체를 클라이언트로 밀어 넣기보다, 검색어를 읽는 부분만 작게 뺍니다.
예를 들어 문제가 있던 코드가 이런 모양이었다고 봅니다.
'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchPage() {
const searchParams = useSearchParams();
const q = searchParams.get('q') ?? '';
return <div>검색어: {q}</div>;
}
이 상태에서는 페이지 파일 전체가 클라이언트 컴포넌트가 됩니다. 먼저 훅을 쓰는 부분을 SearchContent.tsx로 분리합니다.
'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchContent() {
const searchParams = useSearchParams();
const q = searchParams.get('q') ?? '';
return <div>검색어: {q}</div>;
}
이렇게 나누면 부모 페이지는 다시 Server Component로 둘 수 있습니다. 핵심은 파일을 나누는 행위 자체가 아니라 useSearchParams()를 쓰는 영역을 작게 만드는 것입니다.
2분 안에 부모 페이지에서 Suspense로 감쌉니다
이제 page.tsx에서 분리한 컴포넌트를 감쌉니다.
import { Suspense } from 'react';
import SearchContent from './SearchContent';
function SearchFallback() {
return <div>검색 조건을 불러오는 중입니다.</div>;
}
export default function SearchPage() {
return (
<main>
<h1>검색 결과</h1>
<Suspense fallback={<SearchFallback />}>
<SearchContent />
</Suspense>
</main>
);
}
Next.js 공식 useSearchParams 문서는 프리렌더링되는 경로에서 이 훅을 호출하면 가까운 Suspense 경계까지 클라이언트 렌더링 대상이 될 수 있다고 설명합니다. 그래서 감싸는 위치는 페이지 전체보다 작을수록 좋습니다. 검색창, 필터, 정렬 버튼처럼 쿼리 문자열을 직접 읽는 부분만 감싸면 정적 셸을 더 많이 남길 수 있습니다.
fallback은 거창할 필요가 없습니다. 빈 문자열보다 간단한 스켈레톤이나 "검색 조건을 불러오는 중입니다" 같은 문구가 낫습니다. 사용자는 화면이 멈춘 것인지, 검색 조건을 읽는 중인지 구분할 수 있어야 합니다.
dynamic 설정으로 덮기 전에 의도를 확인합니다
오류를 검색하면 dynamic = 'force-dynamic' 같은 설정도 보입니다. 하지만 이것을 바로 붙이면 정적으로 만들 수 있던 페이지를 요청 시 렌더링하는 방향으로 바꿀 수 있습니다. Next.js 공식 오류 문서도 정적 생성을 유지하려면 useSearchParams()를 호출하는 가장 작은 하위 트리를 Suspense로 감싸는 선택지를 먼저 제시합니다.
반대로 페이지가 요청마다 달라져야 하는 화면이라면 동적 렌더링이 맞을 수 있습니다. 이때는 "왜 이 페이지가 매 요청마다 달라져야 하는가"를 설명할 수 있어야 합니다. 초보 단계에서는 빌드 오류를 없애기 위한 임시 처방으로 dynamic 설정을 붙이지 않는 것이 더 안전합니다.
AI에게 다시 요청할 때는 이렇게 말합니다.
방금 수정은 useSearchParams를 쓰는 컴포넌트를 Suspense로 감싸는 방식이어야 합니다.
dynamic 설정을 추가하기 전에, 이 페이지가 정적 렌더링을 유지해도 되는지 먼저 판단해 주세요.
정적 유지가 가능하면 Suspense 방식으로 최소 수정안을 보여 주세요.
이 문장을 넣으면 AI가 설정을 크게 바꾸기 전에 의도를 확인하게 만들 수 있습니다.
마지막 30초에는 빌드와 화면을 같이 확인합니다
수정 뒤에는 개발 서버 화면만 보지 말고 빌드를 다시 돌립니다.
npm run build
통과했다면 다음 5가지를 확인합니다.
1. useSearchParams Suspense 오류가 사라졌는가
2. 검색어가 있는 URL에서 값이 화면에 표시되는가
3. 검색어가 없는 URL에서도 화면이 깨지지 않는가
4. fallback 문구가 너무 오래 남아 있지 않은가
5. 수정 범위가 검색 파라미터 컴포넌트 주변으로 제한됐는가
여기까지 통과하면 배포 전 막힌 문제는 대부분 정리됩니다. 그래도 실패하면 이번에는 오류 전체를 다시 붙이되, "Suspense로 감싼 뒤에도 실패한다"고 조건을 명시합니다. 같은 질문을 반복하는 것보다 수정 전후 차이와 새 오류 줄을 같이 주는 것이 AI 코딩 도구의 답변 품질을 훨씬 높입니다.
이번 오류는 Next.js를 잘못 배웠다는 신호가 아닙니다. next dev와 next build의 렌더링 조건이 다르게 보일 수 있다는 신호에 가깝습니다. 오늘은 오류 줄, 작은 컴포넌트 분리, 부모 Suspense, fallback, 재빌드만 확인하면 충분합니다.
참고 출처
- Next.js Docs, Missing Suspense boundary with useSearchParams: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
- Next.js Docs, useSearchParams: https://nextjs.org/docs/app/api-reference/functions/use-search-params
- Next.js Docs, Entire page deopted into client-side rendering: https://nextjs.org/docs/messages/deopted-into-client-rendering
다음으로 읽을 기사
같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.
첫 번째 댓글을 남겨보세요
여러분의 생각이 다른 독자에게 도움이 됩니다.
댓글 0
이 글을 읽은 독자들의 생각을 나눠보세요.