vercel/next.js · error · BailoutToCSRError
${expression}
Error message
${expression} What it means
This error is thrown when useSearchParams() is called during static prerendering of a Server Component in the App Router. Next.js requires access to search params to be dynamic (request-time); during a static build there is no search params value available, so the renderer throws a BailoutToCSRError with the expression string (e.g. `useSearchParams()`) to signal that this tree must fall back to client-side rendering or be wrapped in a Suspense boundary.
Source
Thrown at packages/next/src/server/app-render/dynamic-rendering-client.ts:116
case 'prerender-client': {
React.use(
makeClientHookHangingPromise(
workUnitStore.renderSignal,
new ClientHookDynamicError(workStore.route, expression)
)
)
break
}
case 'prerender-legacy': {
if (workStore.forceStatic) {
return
}
if (process.env.__NEXT_EXPERIMENTAL_REACT_BROWSER_BAILOUT) {
// @ts-expect-error TODO: Update @types/react-dom to include the reason argument.
React.use(browser(getUseSearchParamsBailoutReason))
return
} else {
throw new BailoutToCSRError(expression)
}
}
case 'prerender':
case 'prerender-runtime':
throw new InvariantError(
`\`${expression}\` was called from a Server Component. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.`
)
case 'cache':
case 'unstable-cache':
case 'private-cache':
throw new InvariantError(
`\`${expression}\` was called inside a cache scope. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.`
)
case 'generate-static-params':
throw new InvariantError(
`\`${expression}\` was called in \`generateStaticParams\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`
)
case 'request':View on GitHub (pinned to 89d017eac4)
Solutions
- Wrap the component calling useSearchParams() in a <Suspense> boundary (e.g. <Suspense fallback={...}><SearchBar /></Suspense>)
- Move useSearchParams() into a dedicated client component ('use client') and isolate it under Suspense
- Add export const dynamic = 'force-dynamic' to the route if it genuinely needs request-time rendering
- For static export, replace useSearchParams() with reading window.location.search in useEffect, or use next/navigation router handling after mount
Example fix
// before
export default function Page() {
return <SearchBar /> // SearchBar calls useSearchParams()
}
// after
import { Suspense } from 'react'
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SearchBar />
</Suspense>
)
} Defensive patterns
Strategy: validation
Validate before calling
import { Suspense } from 'react'
// Before shipping, verify every useSearchParams() consumer is isolated:
// grep -rn "useSearchParams" app/ components/
// and statically confirm each usage site is inside a <Suspense> ancestor or force-dynamic route. Type guard
function isStaticRoute(config: { dynamic?: string }): boolean {
return config.dynamic !== 'force-dynamic'
} Try / catch
// Not applicable: this is a build-time prerender bailout, not a runtime exception to catch.
Prevention
- Always wrap useSearchParams() consumers in Suspense at the page/layout level
- Keep search-param reading in small leaf client components
- Set export const dynamic = 'force-dynamic' on routes that depend on query strings
- Run next build in CI to catch prerender bailouts before deploy
When it happens
Trigger: Calling useSearchParams() in a component that is statically prerendered (next build without dynamic = 'force-dynamic') and is NOT wrapped in a <Suspense> boundary; importing a client component using useSearchParams into a statically rendered page layout; using a shared header/navbar reading search params in the root layout during static export.
Common situations: Upgrading Next.js versions where previously-tolerated unwrapped useSearchParams became a build error; static export (output: 'export') with a component reading search params; adding a search-param-aware widget to a layout; missing Suspense boundary around the client component.
Related errors
- NEXT_DYNAMIC_BAILOUT_REASON
- createRevalidateDuringRenderError (revalidate* called during
- Route "${route}": Unexpected cache miss after cache warming
AI-assisted analysis of vercel/next.js@89d017eac4 (2026-08-27).
Data as JSON: /api/errors/ab41482c692de754.
Report an issue: GitHub.