missing suspense boundary는 훅이 잘못된 게 아니라 useSearchParams가 정적 빌드에서 쿼리를 읽는 동안 대체 화면이 없어 넥스트가 멈춘 줄입니다. 쿼리 컴포넌트만 Suspense로 감싸고 루트 전체를 덮지 않습니다. Next.js App Router, 한국 1인 개발자 기준. 2026년 9월 Next.js missing suspense 문서.
missing suspense boundary는 훅이 잘못된 게 아니라, useSearchParams가 클라이언트에서 쿼리를 읽는 동안 서버가 미리 그릴 대체 화면이 없어서 넥스트가 빌드를 멈춘 줄입니다. 개발 서버에선 넘어가다가 next build만 빨간 줄이 납니다.
페이지를 정적으로 뽑으려다 검색 파라미터가 끼면 렌더가 요청마다 달라집니다. 그 경계를 감싸지 않으면 빌드가 CSR 베일을 허용하지 않아요.
Filters는 클라이언트 컴포넌트여야 훅을 씁니다. 페이지는 서버로 남겨 두면 본문 HTML은 빌드에 남고, 쿼리 부분만 클라이언트가 채웁니다. fallback 문구를 비우면 빌드는 통과해도 사용자가 빈 칸을 봅니다.
페이지 전체가 아니라 쿼리를 읽는 칸만 감싼다
페이지 전체를 감싸면 안 되는 이유
루트 레이아웃을 Suspense로 덮으면 정적 본문까지 같이 미뤄집니다. 빌드는 통과할 수 있어도 첫 페인트가 늦어요. 검색 파라미터와 무관한 제목·본문은 서버 HTML에 남겨 두는 편이 낫습니다.
export const dynamic = 'force-dynamic'으로 페이지를 아예 동적으로 두는 방법도 있습니다. 쿼리가 핵심인 관리자 화면이면 이 선택이 단순합니다. 마케팅 랜딩처럼 캐시가 중요한 페이지에 쿼리 필터 하나 있다고 전체를 동적으로 바꾸면 HTML 캐시가 사라집니다.
에러 바운더리와 혼동하지 마세요. error.js는 런타임 예외용이고, Suspense는 아직 안 온 데이터를 기다리는 칸입니다. 빌드가 컴파일 에러로 죽으면 이 글 대상이 아닙니다. 전역 에러 화면은 넥스트 에러 처리 글을 보세요.
[ ] 빌드 로그에서 useSearchParams를 쓴 파일을 찾았다
[ ] 그 컴포넌트만 client로 두고 Suspense로 감쌌다
[ ] fallback에 같은 높이 자리를 넣었다
[ ] 루트 레이아웃 전체를 감싸지 않았다
[ ] 쿼리가 핵심인 페이지만 force-dynamic을 검토했다
동적 강제보다 경계 | 필터 하나 때문에 페이지 전체를 force-dynamic으로 두면 캐시가 통째로 빠집니다. 훅을 쓰는 칸만 감싸는 쪽이 기본입니다.
빌드 로그로 범인 컴포넌트 찾기
스택 맨 위 파일부터 엽니다. 공유 UI 키트에 훅이 들어 있으면 여러 페이지 빌드가 한꺼번에 죽습니다. 그 키트를 쓰는 모든 페이지에 Suspense를 반복하기보다, 키트 바깥에서 한 번 감싸는 래퍼를 두는 편이 낫습니다.
로컬에서 next build를 돌려 재현하세요. 개발 서버만 보면 이 줄은 안 나옵니다. 힙이 모자란 빌드 실패와 메시지가 다르니, 메모리 부족은 heap out of memory 글로 나누세요.
한국에서 넥스트 앱 라우터로 목록+필터를 만들면, 필터 바를 레이아웃에 직접 넣는 순간 이 줄을 만납니다. 레이아웃은 감싸고, 필터는 자식 슬롯에서 Suspense로 여는 구조가 안전합니다.