#render performance

1 messages · Page 1 of 1 (latest)

wooden moon
#

100 widgets on a single page.
Each widget making its own API call.
Complex CMS-driven pages.

How would you handle render performance?

analog edge
#

The latest version of Puck supports virualization

#

See last post in #releases, you can enable via _experimentalVirtualization prop. Might work.

wooden moon
#

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.

chilly heart
#

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.

Puck - Create your own AI page builder

Create your own AI page builder

wooden moon
#

Thanks. I will share the details of architecture design