-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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_restaurantandload_menusare 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
MenusPagecomponent 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
Suspenseensures 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.