Volto: Recommended pattern for data fetching whilst server-side?

I'm wondering if there's a recommended pattern for how to do data additional data fetching during SSR? For example, if I'm creating a listing variation and need to fetch some additional information about an item that isn't provided by content.items, how can I ensure the SSR waits for me to make additional REST API calls? Currently I'm pushing alot of this work client-side:

const [itemWithData, setItemWithData] = useState(null);
  useEffect(() => {
    const api = new Api();
    api.get(item['@id']).then((itemData) => {
      setItemWithData(itemData);
    });
  }, [item]);

If there is a common pattern, it would be nice if it were mentioned in the Volto documentation too! :slight_smile: Coming from Next.JS where there's clear boundaries of when non-client data fetching happens and extensive documentation around this, I find it a gap in the documentation right now.

Don't know if it makes a difference, but I'm almost exclusively using functional components + hooks.

For optimal results the first SSR request (the listing request) would already have the extra information. It's worth investigating that route.

There's a somewhat similar mechanism to Next.JS', see
Server-side rendering for async blocks – Frontend – Blocks — Plone Documentation v6.0-dev, though I don't know if you'd be able to do "two stage async", we've only tried it for a single stage of fetching async SSR. Some fancy footwork may do the trick here.

The data referenced in the doc example is the listing block "configuration data", so you can write your own wrapper on the listing blocks' getAsyncData and await your additional async data.

I still think the optimal path is making sure the extra information is already served in the first call. Some enhancements to plone.restapi would be needed, but it would be worth.

1 Like

Just a quick follow up (the getAsyncData worked really well for the initial SSR, thanks!). Once the client has loaded all the JS, it re-renders without the result of getAsyncData, causing it to lose the additional data I requested. Am I missing something?

A short example. I'm after the preview_image field so request the full object from the listing. The image appears on the initial render (proven if I disable JavaScript in the browser), but after hydration the image is lost.

async call:

getAsyncData: ({ dispatch, data, path }) => {
      data.querystring.fullobjects = true;
      return getListingBlockAsyncData({ dispatch, data, path });
    },

React component:

const CardListing = ({ items }) => {
  return (
      <Card.Group>
        {items.map((item) => {
          return (
            <Card
              key={item['@id']}
              href={flattenToAppURL(item['@id']}
            >
              <Image src={item['preview_image']?.download} />
              <Card.Content header={item.title ? item.title : item.id} />
              <Card.Content description={item.description} />
            </Card>
          );
        })}
      </Card.Group>
  );
};

No, you're not missing anything. The SSR async support is half-baked, for historical reasons. The components that needed the SSR (navigation, breadcrumbs, etc) were implementing it as rendering side-effects (so the entire data flow is contained in the component) and they haven't been refactored yet. I still think they should be refactored, and move their data fetching entirely in the asyncConnect wrapper, which gets executed not only for SSR, but also client-side page load events. There's a proof of concept on that one: Use only App.jsx asyncConnect for getNavigation dispatching by tiberiuichim · Pull Request #2751 · plone/volto · GitHub

Try to see, in the client side in the CardListing component, if the data exists in the store (maybe in the asyncConnect key?).