Server Side Rendering In Custom Components with GetAysncProps()

Because the Visual Editor renders your page’s content in an iFrame and relies on client-side rendering, data fetched server-side as a prop will not be present in the Visual Editor’s preview and could cause content to be missing or broken. A great way to make sure that your client-side content is synced with your server-side content is to use our getAsyncProps helper.

Let’s say you are using Next.js with the page router and you have an API call that fetches multiple articles. Your getStaticProps function that runs on the server might look something like this:

import React from "react";
import { BuilderComponent, builder, useIsPreviewing } from "@builder.io/react";
import { getAsyncProps } from '@builder.io/utils';

// Define a function that fetches the Builder
// content for a given page
export const getStaticProps = async ({ params }) => {
  // Fetch the builder content for the given page
  const content = await builder
    .get("page", {
      userAttributes: {
        urlPath: "/" + (params?.page?.join("/") || ""),
      },
    })
    .toPromise();

  await getAsyncProps(content, {
    async Articles(props) {
      // console.log('props',props)
      return {
        data: await fetch(`${yourUrlGoesHere}`).then(res => res.json()),
      };
    },
  });

  // Return the page content as props
  return {
    props: {
      page: content || null,
    },
    // Revalidate the content every 5 seconds
    revalidate: 5,
  };
};

We already know that builder.get() is pulling in the content JSON from the appropriate page. getAsyncProps() has two arguments. The first is content, which takes in a content JSON from Builder. The next argument is a named function that returns data. getAsyncProps() looks through this process and finds every component in the content JSON that matches the name of the function in the second argument. For example, in the code above, getAsyncProps() will look through your page content returned from builder.get() for all components named “Articles” and add ‘data’.

In our case that Articles component is a custom component which we have defined below:

import React, { useState, useEffect } from 'react';

const Articles = (props) => {
  const [data, setData] = useState(props.data); // Initialize state with props.data
  console.log('props', data)
  useEffect(() => {
    if (!props.data) { // If no data is passed through props, fetch data
      console.log('whoops! no data from server')
      fetchResults().then(fetchedData => {
        setData(fetchedData); // Update state with fetched data
				console.log('data fetched on the client');
      });
    } else {
      console.log("data is loaded");
      setData(props.data);
    }
  }, []); // Dependency array is empty, so this effect runs only once on mount

  return (<>
    <div>Your Component That Uses The Data Goes Here</div>
  </>);
}

export default Articles;

In an ideal situation in the browser where loading the page via SSR is not an issue, the data variable will be set by just looking at props.data. Because getAsyncProps only runs on the server, in those instances where you are only rendering client-side, for example in the Builder Visual Editor, we still need to fetch the data. You can see this in action in the useEffect above. Essentially we check if there is no data from the server, call some function (in our case called fetchResults) to fetch from the same API as we did in getAsyncProps, and use this as a backup. That way we are able to use our server side rendering on our live website for SEO and performance, and we are still able to visualize and preview our content correctly in the visual editor.

Related links:

2 Likes