#render performance
1 messages · Page 1 of 1 (latest)
The latest version of Puck supports virualization
See last post in #releases, you can enable via _experimentalVirtualization prop. Might work.
I tried. it not working.
The main issue is that SSR is blocked because the page JSON is processed first and all widget dynamic data (API calls) are fetched in parallel before rendering starts.
Current flow:
Page Request
↓
Get Page JSON
↓
preparedContentItem()
↓
await Promise.all(all widget API calls)
↓
wait for 20+ ProductsCarouselWidget APIs
↓
return full HTML
Because of this, SSR is completely blocked until all widget APIs finish. This increases TTFB significantly on complex CMS-driven pages.
A possible solution is Streaming Server-Side Rendering, where the server can stream HTML progressively instead of waiting for all widgets to complete.
However, the major limitation is that Puck currently renders widgets as Client Components. Since Client Components cannot directly perform server-side streaming/data fetching like Server Components, the streaming benefit is limited.
If widgets can be converted into Server Components (or wrapped with Server Components responsible for data fetching), then each widget could:
Fetch data independently on the server
Stream progressively using Suspense boundaries
Avoid blocking the entire page SSR
Improve initial TTFB and perceived performance
Is there a way to do that?
import type { UserConfig } from "./puck-types.ts";
export const config: UserConfig = {
components: {
HeadingBlock: {
render: ({ title }) => {
return (
<ServerComponentWrapper>
<div style={{ padding: 64 }}>
<h1>{title}</h1>
</div>
</ServerComponentWrapper>
);
},
},
},
};
How can we add wrapper like this because puck is client side config.
Hey @wooden moon, I'd suggest going over the RSC guide in our docs. However, pure server components in the editor can't really be used because they need to be rendered on the client when you drag and drop. Pure RSC can still be used to render the final page though, and for that you can use one of the approaches outlined in the guide (If you need to do fetching you probably want to have a different render function for server/client).
I think this is more likely a bottleneck in your rendering pipeline, component design, or backend services, since 100 API calls per component seems pretty extreme. I'd suggest thinking about when you actually need to fetch this data, and whether you can consolidate some of those API calls across components (making 1 call instead of 20).
I don't fully know what your setup looks like, so I'm making some assumptions.
For example, instead of calling an API for each item in a list, could you call the API once for the whole list? You mentioned ProductsCarouselWidget (which I'm assuming is a component), and it sounds like it may be calling an API for each item in the carousel. Could that be reduced to a single request for the entire carousel?
Also, could some of this fetching move to the client side? For example, you could use stale data for the initial render, then fetch the latest data client-side for the editor. For the final page, you could still keep everything fully server-side if needed.
Again, I'm not exactly sure how your app is architected or how everything is designed, but you also mentioned preparedContentItem, which sounds like it fetches content for each component or widget. If that's the case, I'd strongly suggest finding a way to batch or group those calls into a single request.
Thanks. I will share the details of architecture design