-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RSC: How to realize shared code with client-specific execution results #26460
Comments
For the RTK Query example the idiomatic way would to be to fork the createQuery implementation into a “react-server” specific implementation.
The import and api can work the same way in both environments from the consumers perspective. When calling the react-server version of createApi it can generate a stub version of the Hook which throws a helpful error when used in Server Components or just exclude it all together. The principle that underlines this is static optimization of various combinations. In the ecosystem we shouldn’t have to boot code that includes all possible runtime branches. We will also move to use export conditions for production/development for example to support ESM for similar reasons. This goes both ways that the client shouldn’t have to download server specific things for the branches that uses server-only things but the server also shouldn’t have to boot a bunch of code that isn’t relevant on the server. So that’s why it’s worth having two separate implementations/entry points to the api that can each be optimal. Yes, it’s a burden to library authors to optimize code but it’s a one time thing that helps the end user at the end of the day. |
For the Apollo section questions:
This won’t necessarily give you a single one on the client since the client cache can be refreshed but that might actually be a feature you want. For other cases you can use a module level variable only on the client but it really depends on how you’re getting this because it has to be called inside React render for it to be contextual to a request. Not sure how you’re thinking about the rest of the api.
If the component is meant to execute during SSR and then hydrate, it’s a client component and it works the same. As long as it’s used in a client component scope. It doesn’t work to have a library generate a client component from a server component on the fly thought. The code for them must be built by the bundler and we have to know to be build them before it happens. So this doesn’t really work in the general sense without the user placing its code somewhere behind a “use client” in the bundler graph.
Not sure what this means. Is it referring to “SSR” then it’s just a client component. |
@sebmarkbage thanks for the Apollo-part answers, but I think we can ignore that part, which is why I collapsed it :) |
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you! |
@gaearon asked me to open an issue on this, so I could add a bit more context than fits into a twitter thread.
This all has come up within the last few days that I have been trying out the NextJs
/app
folder to find out what we have to do from the Apollo Client and Redux sides to support this.I'm sorry if some of these thoughts come from "a wrong place of understanding" - RSC are still new to me, and honestly at this point I feel more confused about this than the first time I was learning React. If I'm on a completely wrong track somewhere, please correct me.
Example 1: RTK Query
createApi
.createApi
is a function that is invoked with a description of API endpoints and creates a fully-typed reducer, middleware, selectors - and, if thereact
module is active, hooks.This can for example look like this:
From a RSC perspective, it might already make sense to create a Redux store, add the reducer & middleware,
await
the result of thepokemonApi.endpoints.getPokemonByName.initiate
thunk (to use it in a RSC), serialize the cache and rehydrate it on the client later (in case a client component later wants to access that same API endpoint).From a client perspecive, the same store setup will take place and components will mostly call the
useGetPokemonByNameQuery
hook.Now, it doesn't seem that there is any way that we mark
useGetPokemonByNameQuery
as"use client"
, which will prevent any kind of static analysis and early warnings - we will have runtime warnings later when non-server-hooks are called within thatuseQuery
hook.An alternative solution would be to tell users to duplicate this code and call
createApi
from'@reduxjs/toolkit/query/react'
on the client and'@reduxjs/toolkit/query/react'
on the server.That doesn't seem feasible, though:
So for now, we will probably just not add
"use client"
anywhere until some kind of pattern comes up, but I'm not particularly happy about it.Example 2: an Apollo "next-specific" helper library.
This one is collapsed since it is not relevant anymore
With classic SSR, for Apollo, we have told people how to create clients for different NextJs render contexts and how to hydrate data from SSR/SSG to the Client.
With RSC, this picture gets a lot more complicated, and we want to create a helper library that's as easy to use as possible.
Problems we intend to solve contain, among others:
makeClient
function in which the user can "build up" their Apollo Client, with all the individual configuration options.The first approach was to create a
registerApolloClient(makeClient: () => ApolloClient)
to register themakeClient
function, paired with agetClient()
function that would lazy-initialize a client and store it differently in different enviroments:requestAsyncStorage
and create a singleton per-request instance thereReact.cache
call (that, at this point, I hope exists per-request)That didn't work. I couldn't find a place in the code to call
registerApolloClient
that would actually execute this initialization function both on the server and the client. The whacky workaround would be to tell the user to create a server file and a client file and callregisterApolloClient
in both, but tbh., this is something I absolutely want to avoid.But even that seems unlikely: what if the server just rerenders a subtree? Where would I put
registerApolloClient
in that case, to make sure it has been called andgetClient()
doesn't try to call an unregisteredmakeClient
function?So I changed the design of the function:
This way, I can make sure that wherever
getClient
is called,registerApolloClient
has been executed in the same environment before.But then we get to the Provider.
At this point, the user has to wrap
<ApolloProvider client={getClient()}>
around their application so all client components have access to that. (This assumes thatApolloProvider
is a client component, which is another rabbit hole about bundling that we won't go into at this point.)But that doesn't work - the client is non-serializable, so it cannot be created in a Server Component.
As a result, we have to tell our users to create a file like
and then wrap that
MyApolloProvider
component around their application.As this seems like very annoying boilerplate, my idea was that
registerApolloClient
could be extended:But at that point, if I want to be able to execute
registerApolloClient
on the server, the file creatingMyApolloProvider
cannot be marked"use client"
. But without that,MyApolloProvider
will not be rendered on the client.Which leads to the question if it is in any way possible to create but not render client-side components on the server. They would just need to "be in the JSX".
Right now, the workaround is that
registerApolloClient
returns awithClient
function that can be used to wrap hooks that should be used on the client in a way that they callgetClient
instead of using context at all. But this seems pretty hacky:So yeah, this is pretty open ended, but several questions have popped up during the process of getting into all of this, so I'll try to spell out the obvious ones, and maybe you also spotted other questions within my approaches that I didn't even think of asking.
Basic questions:
root.render
?The text was updated successfully, but these errors were encountered: