Skip to content

[RSC Related] Implement async_props for Streaming SSR with Asynchronous Data Fetching #509

@AbanoubGhadban

Description

@AbanoubGhadban

Issue Description

Problem

The current implementation of RORP requires all data to be fully prepared on the Ruby side before initiating Server-Side Rendering (SSR). This introduces significant delays, particularly for pages that depend on multiple asynchronous data sources.

Proposed Solution

Introduce an async_props property to the stream_react_component method that accepts Ruby callbacks. These callbacks will be executed on a background thread, and their results will be sent to the renderer asynchronously when ready. This will enable streaming rendering without waiting for all data to be resolved upfront.

Details

The async_props property will:

  • Accept a hash of lambdas or Procs, where each key corresponds to a prop and the value is a callback that fetches the data.
  • Execute these callbacks in parallel on background threads.
  • Send the results to the React renderer incrementally as they become available.
  • Ensure smooth integration with existing SSR processes.

Example usage in an ERB template:

<% load_restaurant = ->() { Restaurant.find(@restaurant_id) } %>
<% load_menus = ->() { Menu.where(restaurant_id: @restaurant_id) } %>

<%= stream_react_component(
  "MenusPage",
  async_props: { restaurant: load_restaurant, menus: load_menus },
  prerender: true
) %>

In this example:

  • load_restaurant and load_menus are asynchronous callbacks that fetch restaurant details and menu data, respectively.
  • These callbacks will execute on background threads, and their results will be passed to the MenusPage component as they resolve.

React component example:

// RestaurantInfo Component
const RestaurantInfo = async ({ restaurantPromise }: { restaurantPromise: Promise<Restaurant> }) => {
  const restaurantInfo = await restaurantPromise;
  return (
    <div>
      <h2>{restaurantInfo.name}</h2>
      <p>{restaurantInfo.description}</p>
    </div>
  );
};

// Menu Component
const Menu = ({ menu }: { menu: Menu }) => (
  <li>
    <h3>{menu.name}</h3>
    <p>{menu.description}</p>
  </li>
);

// Menus Component
const Menus = async ({ menusPromise }: { menusPromise: Promise<Menu[]> }) => {
  const menus = await menusPromise;
  return (
    <ul>
      {menus.map((menu) => (
        <Menu key={menu.id} menu={menu} />
      ))}
    </ul>
  );
};

// MenusPage Component
const MenusPage = ({ restaurant, menus }: { restaurant: Promise<Restaurant>, menus: Promise<Menu[]> }) => (
  <div>
    <Header />
    <Suspense fallback={<div>Loading restaurant information...</div>}>
      <RestaurantInfo restaurantPromise={restaurant} />
    </Suspense>
    <Suspense fallback={<div>Loading menus...</div>}>
      <Menus menusPromise={menus} />
    </Suspense>
    <Footer />
  </div>
);

Benefits

  • Reduced Rendering Delays: Pages start rendering as soon as some data is available, instead of waiting for all props to resolve.
  • Improved UX: React’s Suspense ensures users see meaningful fallback content while data loads in the background.
  • Efficient Data Handling: Background threads ensure asynchronous data fetching without blocking the rendering pipeline.

Outcome

By implementing async_props, RORP will support streaming SSR with asynchronous data fetching, enabling faster and more efficient server-side rendering. This change will modernize the framework, making it more suitable for real-world applications with complex data dependencies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions