Masonry Server-Side Rendering#
The Masonry component in Ant Design does not render any items during server-side rendering (SSR). This is a known behavior with two distinct layers: a missing-content bug and an inherent layout limitation.
Root cause: mergedItems is populated in useEffect#
mergedItems is initialized as an empty array via useState and is only populated by copying the items prop inside a useEffect. useEffect does not run during renderToString or any other server-side React rendering. As a result, mergedItems remains [] on the server, CSSMotionList receives no keys, and the container renders with zero children and height: 0.
This has been reported in #59455 and #59454 (antd 6.6.5, React 18/19).
Inherent layout limitation: positions require DOM measurement#
Even if the missing-content bug is fixed, absolute positioning will always be unavailable on the server. Column positions and top offsets are computed by usePositions from itemHeights, which are collected in collectItemSize via getBoundingClientRect(). That API only exists in a browser environment.
After client hydration, collectItemSize runs (triggered by a second useEffect watching mergedItems and columnCount) , measures each item's rendered height, and recomputes positions. Until those measurements complete, all items have top: undefined and column: 0.
The layout flow in the browser is:
- Items render with no absolute position (stacked at top-left).
collectItemSizefires after mount, measuring each item's height viagetBoundingClientRect.usePositionscomputes{ column, top }per item — greedily assigning each to the shortest column, or to an explicitly supplieditem.column.- Items rerender with absolute
insetInlineStartandtopstyles applied. - The container's
heightis set to the tallest column height.
Workaround#
To fix the missing-content problem, derive mergedItems synchronously:
// Replace useState + useEffect with useMemo:
const mergedItems = React.useMemo(() => items ?? [], [items]);
This puts item content into the server-rendered HTML. Items will still be unpositioned (stacked) on the server and will shift into their columns after client-side measurement — that is an expected limitation of a browser-measured masonry layout.
Key source files#
| File | Purpose |
|---|---|
components/masonry/Masonry.tsx | Main component; contains the mergedItems state and useEffect |
components/masonry/hooks/usePositions.ts | Computes { column, top } from measured item heights |
components/masonry/MasonryItem.tsx | Renders individual items with layout styles |
| #59455 | Bug report with diagnosis and suggested fix |