vercel/next.js · error · BailoutToCSRError

NEXT_DYNAMIC_BAILOUT_REASON

Error message

NEXT_DYNAMIC_BAILOUT_REASON

What it means

This error is thrown when next/dynamic is used inside a Server Component with ssr: false during static prerendering. Because ssr: false means the component must only render on the client, but a Server Component prerender cannot bail out to CSR without a boundary, Next.js throws BailoutToCSRError with the reason NEXT_DYNAMIC_BAILOUT_REASON to force the tree to become client-rendered.

Source

Thrown at packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx:31

const getNextDynamicBailoutReason = createReactBrowserBailoutReason.bind(
  null,
  NEXT_DYNAMIC_BAILOUT_REASON
)

/**
 * Signals during server rendering that this subtree should be client-rendered.
 */
export function BailoutToCSRForNextDynamic({
  children,
}: BailoutToCSRForNextDynamicProps) {
  if (process.env.__NEXT_EXPERIMENTAL_REACT_BROWSER_BAILOUT) {
    // @ts-expect-error TODO: Update @types/react-dom to include the reason argument.
    use(browser(getNextDynamicBailoutReason))
    return children
  }

  if (typeof window === 'undefined') {
    throw new BailoutToCSRError(NEXT_DYNAMIC_BAILOUT_REASON)
  }

  return children
}

View on GitHub (pinned to 89d017eac4)

Solutions

  1. Move the dynamic(..., { ssr: false }) call into a client component (add 'use client' at the top of the file that calls next/dynamic) and import that wrapper from the server component
  2. Remove ssr: false if the component can be server-rendered, using a mounted-state check for client-only APIs instead
  3. If the dynamic usage must stay in server code, ensure the component is under a Suspense boundary and that you're on a version that supports the bailout rather than throwing

Example fix

// before (server component page.tsx)
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), { ssr: false })
export default function Page() { return <Chart /> }

// after
// ChartWrapper.tsx
'use client'
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), { ssr: false })
export default function ChartWrapper() { return <Chart /> }

// page.tsx (server)
import ChartWrapper from './ChartWrapper'
export default function Page() { return <ChartWrapper /> }
Defensive patterns

Strategy: validation

Validate before calling

// Convention check before adding next/dynamic with ssr:false in server code:
import fs from 'node:fs'
const src = fs.readFileSync('app/page.tsx', 'utf8')
if (src.includes("ssr: false") && !src.trimStart().startsWith("'use client'")) {
  throw new Error('ssr:false dynamic import must live in a client component')
}

Type guard

function isClientComponent(source: string): boolean {
  return /^['"]use client['"]/.test(source.trimStart())
}

Try / catch

// Not applicable: thrown during static prerender at build time; fix structure instead of catching.

Prevention

When it happens

Trigger: Using dynamic(() => import('...'), { ssr: false }) inside a Server Component (a file without 'use client'); a shared component/library that internally uses next/dynamic with ssr: false being imported by a server layout/page; wrapping the ssr:false dynamic import in a Suspense boundary so the bailout propagates during prerender.

Common situations: Adding a client-only widget (map, editor, chart) with next/dynamic ssr:false directly to a server page; upgrading Next.js 13/14 where ssr:false in Server Components became disallowed; libraries like react-map-gl wrappers using ssr:false imported from server code; static export setups.

Related errors


AI-assisted analysis of vercel/next.js@89d017eac4 (2026-08-27). Data as JSON: /api/errors/6aad79b1293c09dc. Report an issue: GitHub.