WIP: RSC sub route
Description
RSC Sub routes - Allow Hydrogen to define sub routes with RSC:
Expected behaviour:
- SSR generates content on server-side as a Suspense fallback
- Client side hydrates with a
__rsccall (for now)
Before submitting the PR, please make sure you do the following:
- [ ] Read the Contributing Guidelines
- [ ] Provide a description in this PR that addresses what the PR is solving, or reference the issue that it solves (e.g.
fixes #123) - [ ] Update docs in this repository according to your change
- [ ] Run
yarn changeset addif this PR cause a version bump based on Keep a Changelog and adheres to Semantic Versioning
@frandiox This branch is demonstrating how I think we can do an opted-in RSC sub route implementation without making it a major breaking change.
To play with this, just uncomment out <RSCSubRoute /> in the demo-store index.server.tsx. You will see it is trying to make an rsc call to /test path (which I haven't figure out how I can dynamically register a hydrogen route yet)
I notice that we virtually imports hydrogenRoutes in the handleRequest. Isn't this static? Do we need to re-import this on every worker request? or can we actually save this globally in memory?
I am thinking to dynamically register these sub routes as SSR render discovers them .. that is if we can guarantee to have a SSR render before an RSC request.
Another way is to have these sub routes to be also defined in the routes folder so that they also get discovered by the virtual import.
Thoughts?
@frandiox I updated this PR to demonstrate sub routes working. I think this solution is quite elegant - it doesn't make a major breaking change.
How it works
I put the sub route inside demo store's routes/component folder. Vite picks up the sub route with virtual file routes. Client-side makes the extra rsc call to the sub route.
Thinking about how to get it not make a second __rsc call on full page load
Wondering if I can do a RSC render (along with the fallback <Suspense fallback={cloneElement(page, serverProps)}> and output the rsc output inside in the meta tag with a useID generated id. Then we pass this id along with RSCSubRouteClient so that it can pick up the rsc output that got generated by SSR
Thanks for putting this together! I haven't had much time yet to play with it but, if I didn't understand it wrong, I think there are a few challenges here:
- RSC payload in SSR is still in one big chunk so it cannot be reused later when navigating.
- Even if we are able to split the main RSC payload during SSR in sub-payloads, they still wouldn't make it to the browser cache yet, so perhaps we'd need to use manual in-memory caching before calling
fetchfor navigation? - It seems there's a necessary RSC waterfall request when navigating. I assume this is not a problem for already-cached RSC payloads but it might not be trivial for new navigations. I guess the only way to prevent this is if the browser is aware of what it needs to request beforehand... but that would require some sort of manifest...
Any idea how to deal with any of this? 🤔
A possible approach to explore for the first challenge would be changing RSCSubRoute.server.tsx to do something like this:
rscRenderToReadableStream(actualServerComponent).tee()createFromReadableStream<>{response.readRoot()}</>- save the other teed RSC stream in request object
- render each saved RSC stream to different meta tags during SSR.
- somehow, hydrate this in the browser later using all the different inlined sub-RSC payloads❔
I've no idea how this would affect CPU performance though.
If we send to the client the separate layout maps, then the client should know during transitions which RSC request to make and which not to, so we wouldn't need to rely on browser cache.
WIP Sub route API proposal
All new function naming are not final
How to opt-in using Hydrogen sub route
-
Most likely going to define a new
entry-clientso that the main page content is also a sub route implementation - I haven't fully hash this out yet -
Define sub routes
Global sub routes
Define global sub routes in App.server.tsx and make sure the outlet is included in renderHydrogen. These sub routes will be available on all routes
export const HeaderRSCOutlet = defineRSCOutlet({
outletName: 'HeaderRSCOutlet', // haven't figure out how I can avoid the need to have this string to match the export name
component: Header,
});
function App({request}: HydrogenRouteProps) {
return (
<HeaderRSCOutlet />
);
}
export default renderHydrogen(App, {
HeaderRSCOutlet,
});
Page sub routes
Define page level sub routes in page routes, for example, routes/index.server.tsx. These are sub routes that will only exist for this page route.
const Test = ({id}: {id: string}) => (<p>Test RSC Outlet {id}</p>)
export const TestRSCOutlet = defineRSCOutlet({
outletName: 'TestRSCOutlet',
component: Test,
dependency: ['id'],
});
export default function Homepage() {
return (
<TestRSCOutlet id="123"/>
);
}
Define page level sub routes in page routes, for example, routes/index.server.tsx. These are sub routes that will only exist for this page route.
Wouldn't we want some sub-routes to share? Example from remix:

The global layout would include the left navigation, which is shared across the whole app (like our header & footer), but when on the sales tab, the top navigation is shared for all those "sub routes". How would I describe a tree like this with the proposed API?
Oxygen deployed a preview of your kd-correct-customer-api-docs branch. Details:
| Storefront | Status | Preview link | Deployment details | Last update (UTC) |
|---|---|---|---|---|
| subscriptions | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
| vite | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
| custom-cart-method | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
| third-party-queries-caching | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
| optimistic-cart-ui | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
| skeleton | ✅ Successful (Logs) | Preview deployment | Inspect deployment | March 15, 2024 5:18 PM |
Learn more about Hydrogen's GitHub integration.
Can external headless developers access that shopify slack thread?