@senspond
>
NextJS useSearchParams() should be wrapped in a suspense boundary at page 오류해결
Next.js 15 버전을 사용하여 개발 중, 이전에 rgbitcode
블로그에서는 발생하지 않았던 오류를 확인했습니다. 정확히 어떤 버전부터 이 변화가 일어났는지 확인할 필요가 있지만, rgbitcode
블로그에서는 useSearchParams
를 사용하지 않고, URL을 /blog/content/?id=2
와 같은 쿼리 파라미터 형식이 아니라 /blog/content/2
와 같은 경로 형식으로 처리하고 있었습니다.
app router 방식의 디렉토리 기반 페이지 라우팅
그래서 rgbitcode 블로그는 https://rgbitcode.com/blog/senspond/1 와 같은 경로로 라우팅됩니다.
쿼리 파라미터 기반 페이지 라우팅
하지만 이번에 Next.js로 새 프로젝트를 진행하면서 useSearchParams를 사용했는데, 개발 모드 dev
에서는 오류가 발생하지 않다가, 빌드를 수행하니 오류가 발생하는 문제를 경험하게 되었습니다.
오류 메시지 : useSearchParams() should be wrapped in a suspense boundary at page [페이지 url]
아래는 /project/?id=13 와 같은 형식으로 접근할 수 있는 페이지입니다.
"use client"
export default function UploadAtProject() {
const searchParams = useSearchParams(); // `useSearchParams`를 사용하여 query 파라미터 받아옴
const projectId = searchParams.get("id"); // URL에서 'projectId' 가져오기
const [projects, setProjects] = useState<IProject[]>([]);
const [loading, setLoading] = useState<boolean>(false); // 로딩 상태
useEffect(() => {
const loadProjects = async () => {
setLoading(true); // 데이터 로딩 시작
try {
const response = await fetch(`/api/projects/${projectId}`); // `projectId`를 포함하여 요청
if (!response.ok) throw new Error('프로젝트를 불러오는 데 실패했습니다.');
const data = await response.json();
setProject(data.data); // 프로젝트 데이터 저장
} catch (error) {
console.error('프로젝트 로드 실패:', error);
} finally {
setLoading(false); // 데이터 로딩 완료
}
};
loadProjects();
}, [projectId]);
if (loading) {
return <div>로딩 중...</div>;
}
if (!project) {
return <div>프로젝트를 찾을 수 없습니다.</div>;
}
return (
<div>
<h1>{project.name}</h1>
<p>{project.description}</p>
</div>
);
}
Next.js의 App Router 방식에서는 기본적으로 모든 페이지가 서버 사이드 렌더링(SSR)으로 작동합니다.
빌드 과정에서는 서버에서 먼저 페이지를 렌더링하고, 그 후 클라이언트에서 추가적인 데이터 로딩이나 인터랙션을 처리하게 됩니다.
하지만 useSearchParams()는 클라이언트 사이드에서만 작동하는 훅이기 때문에, 서버 사이드 렌더링 시에는 정상적으로 동작하지 않습니다. 이로 인해 페이지가 처음 렌더링될 때, 클라이언트에서 쿼리 파라미터를 읽지 못해 빈 페이지가 표시됩니다. 이후 클라이언트 사이드에서 데이터를 fetch
를 통해 불러오고 화면에 렌더링되지만, 그 전까지는 빈 페이지가 잠깐 보이게 됩니다.
이 문제를 해결하려면 useSearchParams()
를 클라이언트 컴포넌트로 분리하거나, 서버 사이드에서 사용할 수 있는 다른 방법을 적용해야 합니다.
page.tsx에서 useSearchParams()을 사용하는 클라이언트 컴포넌트를 Suspense 로 감싸주기
import {Suspense} from "react";
import UploadAtProject from "@/app/upload/upload";
export default function UploadPage() {
return (
<Suspense>
<UploadAtProject/>
</Suspense>
)
}
이 해결방법은 NextJS 공식 페이지에 안내되어 있습니다.
https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
Suspense
는 React 18 버전부터 더 본격적으로 지원되며, 서버 사이드 렌더링(SSR)과 클라이언트 사이드 렌더링(CSR) 모두에서 데이터를 비동기적으로 로드할 수 있게 도와주는 중요한 기능입니다.
Suspense
를 사용하면, 클라이언트 사이드에서 데이터가 로드되기 전에 빈 페이지가 보이는 문제를 해결할 수 있습니다. 구체적으로, Suspense
는 데이터를 로딩하는 동안 로딩 스피너나 다른 대체 UI를 보여주고, 데이터가 준비되면 실제 UI를 렌더링하는 방식으로 작동합니다.
React 18에서의 Suspense
사용 예시
import React, { Suspense } from 'react';
// 클라이언트 컴포넌트에서 데이터를 fetch하는 컴포넌트
const ContentComponent = React.lazy(() => import('./ContentComponent'));
function BlogPage() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ContentComponent />
</Suspense>
);
}
React 18에서 Suspense
와 useTransition
을 활용하면, 비동기 데이터 로딩을 더 잘 처리할 수 있습니다. 이 방법을 사용하여 useSearchParams()
와 같은 클라이언트 전용 훅을 사용할 때, 빌드 시 빈 페이지가 보이지 않도록 해결할 수 있습니다.
https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
https://ko.react.dev/reference/react/Suspense
안녕하세요. Red, Green, Blue 가 만나 새로운 세상을 만들어 나가겠다는 이상을 가진 개발자의 개인공간입니다.
현재글에서 작성자가 발행한 같은 카테고리내 이전, 다음 글들을 보여줍니다
@senspond
>