Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/next/src/shared/lib/lazy-dynamic/loadable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ function Loadable(options: LoadableOptions) {
<Loading isLoading={true} pastDelay={true} error={null} />
) : null

// If it's non-SSR or provided a loading component, wrap it in a suspense boundary
const hasSuspenseBoundary = !opts.ssr || !!opts.loading
// Only wrap in a Suspense boundary when a loading component is explicitly provided.
// When no loading component is given, use Fragment to allow parent Suspense
// boundaries to handle the loading state properly.
const hasSuspenseBoundary = !!opts.loading
Comment on lines +51 to +54
Copy link
Copy Markdown
Contributor

@vercel vercel Bot May 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ssr: false and no loading component is provided to next/dynamic(), the Suspense boundary is removed, causing BailoutToCSRError to propagate as a fatal SSR error instead of gracefully bailing out to client-side rendering.

Fix on Vercel

const Wrap = hasSuspenseBoundary ? Suspense : Fragment
const wrapProps = hasSuspenseBoundary ? { fallback: fallbackElement } : {}
const children = opts.ssr ? (
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/shared/lib/loadable.shared-runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ function createLoadableComponent(loadFn: any, options: any) {

return React.useMemo(() => {
if (state.loading || state.error) {
if (!opts.loading) {
return null
}
return React.createElement(opts.loading, {
isLoading: state.loading,
pastDelay: state.pastDelay,
Expand Down