๐บ๏ธ RFC: React Elements and Promises as loader / action data #8048
Replies: 24 comments 42 replies
-
A super hacky, "works on my machine", proof-of-concept was done to see what it might feel like: https://github.com/jacob-ebey/remix-rsc-webpack/tree/main/fixtures/basic/app/routes/about |
Beta Was this translation helpful? Give feedback.
-
Loved the idea! But not sure about the example, wouldn't the proposal still carry the same problem? The loader may be modified to return |
Beta Was this translation helpful? Give feedback.
-
IDK how to fit this into the proposal because I want it to focus more on what it enables, and not how, but I've been thinking about the default route component export as your "entry into the RSC router". RSC router implementations so far essentially deconstruct or re-assemble a tree structure from the RSC payload. In contrast, here each route is an RSC payload you control the shape of and compose however you see fit at a route level. "Per route RSC router". |
Beta Was this translation helpful? Give feedback.
-
I love the new capabilities! My doubts are about the boundaries and hierarchy between RSC and Regular components. |
Beta Was this translation helpful? Give feedback.
-
one thing that we do sometimes in the loader is to cache the data that we load, would it be possible to cache a component rendered in a loader, like in a lru or redis ? |
Beta Was this translation helpful? Give feedback.
-
I like this approach, the migration story will be super simple, let me gradually move parts of my UI to the loader when it makes sense, instead of having to do a big bang migration to SC or mark everything a client components (having to forcefully move components to another file just add The fact that I have a loader that receives a request and sends a response means I could also keep checking auth and doing a lot of other things in the loader before starting to render the SC, for redirects in particular this will be way better than having to do a tacky approach to do a redirect since the loader runs before I start streaming the HTTP response. I can also optimize how I get the data in the loader and then pass the results to the SC to render, or I can let SC do the data retrieval, or a mix of both where most important data is optimally loaded first and leaf data is loaded in the SC. And with useFetcher integration I will be able to do nice things like render a notification bell button that when clicked it fetches the notifications to render inside a dialog, but instead of sending the data I can just send the JSX directly and later do It will also let me avoid sending possible sensitive data only to be used as a React key. |
Beta Was this translation helpful? Give feedback.
-
This is really interesting, it should be possible to deploy the server-side rendering part of the app (the one that outputs HTML) to the edge using Cloudflare Workers so that happens near users, but keep loaders and actions in a single location where I have more direct access to the DB or I'm near my API. |
Beta Was this translation helpful? Give feedback.
-
I wonder if it would be possible to get a stream response from another route and return it from the loader. export async function loader() {
let response = await fetch("a url that returns JSX from another Remix app")
return rsc({ 3p: response.arrayBuffer() })
} This way, I could grab the JSX from another route and compose it inside my own app. |
Beta Was this translation helpful? Give feedback.
-
Hey everyone ๐ , I've only been using remix for a short time and I'm excited for this RFC and the introduction of RCS. I have a question about the multi deployment point. I always thought that we deploy a remix App as single deployment. But if I understand this new paradigm correctly, we could have multiple remix apps with different responsibilities (eg billing) and the "view-layer" remix app. my question is how would the "view-layer" remix app use RSCs from say the billing Remix app? |
Beta Was this translation helpful? Give feedback.
-
Do you have any ideas of how |
Beta Was this translation helpful? Give feedback.
-
With this RFC, is it not possible to pass "client" components as children to server components? Sometimes you have a very static server component that just needs a little client component inside it. |
Beta Was this translation helpful? Give feedback.
-
It feels like the export default name for the component could be a named export instead and called "ClientRoute/Client/ClientComponent" or something, as by that point you're on the client right? and makes it clearer that any code related to the jsx thats returned (excluding the server components) is sent to the client. |
Beta Was this translation helpful? Give feedback.
-
Whats the point of having the loader return the async data, couldn't you move the content of the loader down into the |
Beta Was this translation helpful? Give feedback.
-
what is |
Beta Was this translation helpful? Give feedback.
-
I like the ideal, but I'm not sure about the example, the motivation and the HAETEOAS stuff. the RSC part is what I really want. maybe we can give more detail about that. |
Beta Was this translation helpful? Give feedback.
-
Naming: |
Beta Was this translation helpful? Give feedback.
-
I like that the primitives loader and action remain. I like the addition approach. Sounds to me like there will be no need for "use client", since the RSC is just an addition to render components on the server, not interleave like in nextjs. In nextjs you could technically write server code, get headers and such in every RSC component and nest and interleave with client components, while in remix you would have to enable each such component to have own loader outside of routing, which doesn't feel right. TBH I kind of like this Idea of keep it loader and action and add the ability to return components vs. the interleaving in next. In next you are also forced to create artificially extra files just for the "use client". IN remix having them only come from loader and/or action would eliminate the need. At least if the RSC rendering the same way as the default exported route component (tree), which means you can naturally then use client hooks in the RSC or its children, without the need of separation. |
Beta Was this translation helpful? Give feedback.
-
Would be cool if I could |
Beta Was this translation helpful? Give feedback.
-
what's about streaming? // loader
function loader() {
const { title, content } = await loadArticle();
return {
articleHeader: <Header title={title} />,
articleContent: <AsyncRenderMarkdownToJSX makdown={content} />,
};
} do we need to |
Beta Was this translation helpful? Give feedback.
-
is it a good idea return component from loader/action? is it possible that we can just use server component like Next? |
Beta Was this translation helpful? Give feedback.
-
What about using something similar to Next? export default async function Component() {
// blocks render until getArticle is resolved
const { article } = useLoaderData(await getArticle());
// stream article when it's resolved
const { article } = useLoaderData(getArticle());
return (
<main>
...
</main>
);
} IMHO, in the react world (at least), by having everything close to the component makes it better readable |
Beta Was this translation helpful? Give feedback.
-
Any update regarding this? |
Beta Was this translation helpful? Give feedback.
-
Will rsc be CDN cachable? For example for during page navigation? Afaik it's not on nextjs, but if setup properly it should be possible to return it with a cache header? |
Beta Was this translation helpful? Give feedback.
-
There is a lot of prior art related to this, just in the other direction from traditional server oriented frameworks trying to adapt and add more interactivity. It's pretty nifty to see both sides (MPA and SPA) finally meeting in the middle after all these years. A bit of context or side reading for anyone who might be interested: Phoenix LiveView (original release in 2018, 1.0 release is recent) Phoenix was one of the pioneers of this approach. The link is a post by the author of LiveView (and Phoenix, and Elixir) who even makes mention of React Server Components and the parallels. Htmx A primer on HATEOAS. HTMX itself is a library that implements this sort of behavior ร la carte. Couple of other notable LiveView style frameworks you may have heard of, tossed in for good measure. Rails Hotwire Laravel Livewire |
Beta Was this translation helpful? Give feedback.
-
Summary
This proposal seeks to introduce the ability to return Elements and Promises from loaders and actions in Remix. This will allow developers to return fully rendered components from loaders and actions, enabling a more flexible and robust approach to web application development that closely aligns with the HAETEOAS (Hypermedia as the Engine of Application State) paradigm.
Example
Motivations
"New" programming paradigms
Remix currently adopts a model-view-controller (MVC) like structure:
This structure, while familiar and effective, creates a tight coupling between data retrieval (loaders) and display (view layer), resulting in a potential expected vs retrieved data mismatch between your deployed loaders and the view layer running in your users browser.
Failure Example
Let's imagine we have a user browsing the above application and we deploy a new version that makes the following code change innocently re-naming a property on the article from "content" to "html".
Our user is still browsing the side and their route component doesn't know about the new shape of the article data, that means our route component will be rendering this:
Ultimately we didn't make any changes to what or where data is surfacing, just how the server hands off the data to the view layer.
How to avoid this
Let's change up the paradigm a bit and instead of returning data to our View layer, let's return pieces of view to our View layer. We'll return what and how to render, not just what. This decouples deep data access in the View layer (that can lag behind in deployments and has the same issues that the
/api/v1
,/api/v2
versioning is meant to solve), and instead only couples where to surface things.Let's look at how we can structure the above in this "new" paradigm:
This may not feel like we are doing much here other than moving the
Header
andArticle
components up to the loader, but we have moved the how to display responsibilities of header and article up to the loader (server) and they have become part of our application state, instead of their data and what to display. What does that mean for the example that broke an active user earlier? Well, we can now make the change, the browser loads the payload with the same properties, and is surfacing the new way of showing in the same location.Streaming Data (and Components)
Streaming promises is currently only enabled through the use of the
defer()
utility, and is restircted to only root level promises for data. Leaning into React 18 and the rules around suspense, we can enable streaming of arbitrary promises, including promises that return React elements or event more promises, all through native React features, including the transport format.RSC (React Server Components)
This proposal is enables a HATEOAS-inspired approach to building React applications using Remix, it just so happens that RSC is the technology that enables this. RSC is a technology that allows you to render React components on the server and stream them to clients, hydrating only the interactive bits. This is a huge win for performance and developer experience.
Backwards Compatibility
This proposal is completely backwards compatible with existing Remix applications. The only change is that you can now return React elements and promises from your loaders and actions, instead of just JSON encoded or defer encoded data. This is accomplished by enabling the naked object returns, as well as a new response helper (maybe
rsc(<div/>, { headers: {} })
or something similar) to opt into the new behavior and encoding format for your data.Multi Deployment
A future split at the component / loader / action level in this world also means it would be super easy to split your application into multiple deployments, allowing you to deploy your loaders and actions to a different domain, or even a different hosting provider, than your view layer. This would allow you to scale your application in a way that makes sense for your use-case, and not be tied to a single deployment strategy.
Challenges
Conclusion
This proposal seeks to introduce the ability to return Elements and Promises from loaders and actions in Remix. This will allow developers to return fully rendered components from loaders and actions, enabling a more flexible and robust approach to web application development that closely aligns with the HAETEOAS (Hypermedia as the Engine of Application State) paradigm.
It also just so happens that this proposal is the first step in enabling React Server Components in Remix in a way that is backwards compatible with existing applications, and allows for a smooth transition to a more robust and performant approach to building React applications.
Beta Was this translation helpful? Give feedback.
All reactions