Next.js에서 App Router를 사용할 때, 흔히 다음과 같은 컴포넌트를 작성한다:
"use client";
import { useSearchParams } from "next/navigation";
export default function Example() {
  const params = useSearchParams();
  return <div>{params.get("query")}</div>;
}
이 자체는 문제가 없다. 하지만 이 컴포넌트를 서버 컴포넌트에서 직접 import하면, Next.js 15에서는 다음과 같은 빌드 에러가 발생할 수 있다:
useSearchParams() should be wrapped in a suspense boundary at page "/"
또는
Export encountered an error on /page: /, exiting the build.
Next.js 15부터는 useSearchParams, useRouter, usePathname 같은 CSR 전용 훅이 감지되면, 해당 컴포넌트가 서버에서 직접 사용될 경우 에러를 발생시킨다.
이러한 훅들은 Next.js 내부에서 "CSR Bailout 훅"이라 부르며, SSR 중에는 사용할 수 없기 때문에 에러가 발생하는 것이다.
| 포함된 훅 종류 | 서버 import 가능 여부 | 
|---|---|
useState, useEffect 등 일반 훅 | 가능 | 
useSearchParams, useRouter 등 | 불가 (Suspense 필요) | 
가장 공식적이고 SSR 성능까지 보장하는 방법은 <Suspense>로 해당 컴포넌트를 감싸는 것이다.
// app/page.tsx (서버 컴포넌트)
import { Suspense } from "react";
import Example from "@/components/Example";
export default function Page() {
  return (
    <main>
      <Suspense fallback={<div>로딩 중...</div>}>
        <Example />
      </Suspense>
    </main>
  );
}
ssr: falseSuspense 대신 next/dynamic으로 감쌀 수도 있다.
import dynamic from "next/dynamic";
const Example = dynamic(() => import("@/components/Example"), {
  ssr: false,
  loading: () => <div>로딩 중...</div>,
});
서버 컴포넌트 내에서 클라이언트 훅을 사용하는 부분만 따로 분리할 수도 있다
// ServerComp.tsx
import ClientOnlyHandler from "./ClientOnlyHandler";
export default function SearchForm() {
  return (
    <form>
      <input />
      <ClientOnlyHandler />
    </form>
  );
}
// ClientOnlyHandler.tsx
"use client";
import { useRouter } from "next/navigation";
export default function ClientOnlyHandler() {
  const router = useRouter();
  return <button onClick={() => router.push("/search")}>검색</button>;
}