-
Notifications
You must be signed in to change notification settings - Fork 31
Investigate SSR for RTK Query #88
Comments
When I've done SSR with Redux in the past the basic flow has been
There are 2 basic approaches for how the libs you mentioned support SSR.
The first approach has the advantage of being relatively simple to implement and reason about, but because you have to fetch the initial data yourself it can be difficult to know what data deeply nested components need. On the other hand, multiple renders can be very tricky to implement (especially if you're working within the confines of something like Nextjs). Eventually Suspense for Data Fetching™ will give us the utility of the second approach with the simplicity of the first and we can all stop talking about this and take naps instead. Of the libs you mentioned, only SWR doesn't have any guide or support for the second approach. All support the first approach (React-Query and SWR hooks accept initial data, Urql and Apollo caches can be populated before being provided in the react tree). https://github.com/FormidableLabs/react-ssr-prepass might be a useful resource for y'all - it's what urql uses to do the multiple renders thing, but has no urql-specific bits in it. It looks like the first approach should be possible with RTK Query now - just |
K, that was harder than expected but easier than I feared 😅. My efforts can be found at https://github.com/JNaftali/rtk-query-nextjs-example or https://codesandbox.io/s/sad-sun-deczo - data is successfully fetched both server side and client side. 2 issues I ran into:
Most of |
Used the with-redux-toolkit Next's example as well. Here's my repo So far so well, it seems to work without errors.
I was initially worried you might have to do Let me know If I can do anything else that might be helpful. Edit: [1]: I have only used swr on the client-side with Next.js but react-query seems to provide some functionality for fetching on the server-side and then using react-query client-side: https://react-query.tanstack.com/docs/guides/ssr#integrating-with-nextjs
|
I think @JNaftali summarized the gist of it perfectly! ❤️ Having implemented the Hydration on page navigation While there are a lot of detail and caveats that goes into this, I think RTK Query can support SSR in a bunch of cases in its existing form, with an important exception, Next.js Even if the subscriptions could be moved to the new store, I think performance would be bad and I think another issue would be any asynchronous work currently pending in middlewares. This really is complex and I'm struggling for a way to express it in a way that makes sense, but, the key thing to note here is that both Another caveat with Next.js here is that there are currently no place where you can receive data from the server and do something with it before render. You can see this in the official The React Query SSR-docs might be a decent source of inspiration for how we designed the API there. The Hydration tests could also be valuable to look at, at least what different kinds of cases we are trying to cover. Yet another caveat with Next.js is that since these functions run on the server where you don't have access to client state, you can't really know if you need to fetch new data or not, you will always fetch data on page navigation even if you have "fresh" data in the cache on the client. Not a huge deal and something you kind of have to live with. You might have to account for the case where the client already has fresher data and avoiding merging the "older" data from the server though.. More notes on hydration
Refetching queries after initial render on the client This is something I'm not sure if RTK Query handles today? I don't really have time to test, but if a query has become stale since it was fetched on the server (for example if the html is cached in a CDN, but the query has a cachetime that is shorter), you probably want to refetch that on the client after the initial render has happened? |
Oh, and a note on a part of @JNaftali's example that I know from experience can be hard to understand when not used to Next.js and SSR. On the initial page render that happens on the server, there are actually two stores getting created. One store is created in On the client, yet another store is created which is also initialized with the data from |
I'll probably need to re-read all this a few more times over the next days (and try stuff out) to completely wrap my head around it, so this are my first thoughts that might be based on completely wrong assumptions. ErrorsFor errors, we don't have to assume non-serializable data for now. Everything that is Rehydration/cache mergingIf I have understood this correctly, my theory would be that we could prevent a lot problems by not trying to hydrate/rehydrate the api as a part of the full redux store here (potentially overriding values), but maybe dispatching some kind of That action would probably contain the current api state on the server and current timestamp of the server. This way, the client could just calculate a |
One small correction here. |
This has taken me years to wrap my head around, it's a really complex domain and hard to communicate. 😄 That said, from your response it does seem you've definitely understood the gist of it!
Awesome! I haven't looked at how this is handled in RTK/RTK Query at all so might very well be a non-issue, just thought I'd point out that it's something extra to think about when it comes to SSR.
I wanted to focus on the challenges and not go into solutions too much to keep the post manageable, but I definitely think this is the way to go! It's nice that this approach keeps this concern separated into RTK Query. Since I don't know of any way currently to do this outside of render in Next.js right now, I imagine you might want to abstract away dispatching that action into a
The problem with this is that the response might be cached indefinitely between the server rendering the html markup and the client hydrating it, so the |
@awareness481 Good point, thanks for clarifying! I think what I meant to say was that from the clients perspective, the code that fetches the data from Edit: This made me realize that |
@Ephem's comments were, indeed, right on the money. To sidestep the issues about how nextjs does ssr I decided to make another example which should be free of such bugs using Razzle - https://github.com/JNaftali/razzle-rtk-query-example. Razzle, if you're not familiar, has much less magic than nextjs - it's pmuch just a preconfigured version of webpack + express. I believe (although I am not certain) that it would still be possible to do this using nextjs, but when configuring the store I would have to make the reducer handle a global action to replace the state wholesale (since in nextjs I wouldn't be able to get the server rendered data in time to pass it in to Notably I still get an error when using Hope this is helpful! |
Can you share a link to this repo or CSB? If you're using node-fetch, the signal usage should be the same. |
@msutkowski I made a branch that demonstrates the error - https://github.com/JNaftali/razzle-rtk-query-example/tree/fetchbasequery-not-working. I also copied the error (as copied from the redux state) into a gist - https://gist.github.com/JNaftali/6b7ca6be39a1374c346c70cd1883bba5 |
Hmm. Seems like your node-fetch polyfill is kicking in too late. const AC =
typeof AbortController !== 'undefined'
? AbortController
: class implements AbortController {
signal: AbortSignal = {
aborted: false,
addEventListener() {},
dispatchEvent() {
return false
},
onabort() {},
removeEventListener() {}
}
abort() {
if (process.env.NODE_ENV !== 'production') {
if (!displayedWarning) {
displayedWarning = true
console.info(
`This platform does not implement AbortController.
If you want to use the AbortController to react to \`abort\` events, please consider importing a polyfill like 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'.`
)
}
}
}
} and that does not seem to be compatible with |
@phryneas @JNaftali Just looked through this. The quickest fix is to swap The longer version is to override the razzle config to not mangle output in the terser plugin, which doesn't seem worth it to me. The redux-toolkit types are correct like @phryneas says, but the razzle config is dropping the class when mangling. Relevant issue: node-fetch/node-fetch#784 |
I hadn't heard of |
@JNaftali I'm going to do some experimenting this weekend and see what I can come up with. I've only used Next for prototypes and debugging misc. things, so my exposure is pretty limited. Thanks for providing the repo to help us solve the razzle case. |
Another solution might really be just to PS: apparently not. isomorphic-fetch never sets the global if (!global.fetch) {
global.fetch = module.exports;
global.Response = realFetch.Response;
global.Headers = realFetch.Headers;
global.Request = realFetch.Request;
} |
Couldn't figure out how to get cross-fetch to solve all my problems - the polyfill version ( Putting the following at the top of my import fetch, { Headers, Request, Response } from 'node-fetch';
import AbortController from 'abort-controller';
global.fetch = fetch as any;
global.Headers = Headers as any;
global.Request = Request as any;
global.Response = Response as any;
global.AbortController = AbortController; I'm confused why putting that at the top of |
@JNaftali Thanks for diving into this. I didn't actually try to I'll try to get a Next.js repro going as well to see what steps are needed there. Feel free to ping me on Discord in Reactiflux if you run into any more issues :) |
@msutkowski Nice work! My understanding was that this issue was broader than just the fetchBaseQuery-thing though, was it accidentally closed prematurely? |
Probably an auto-close due to this issue being mentioned in the closed PR :) |
Yup, sorry about that. Shouldn't have been closed. |
Hello everybody, Many thanks for this very insightful thread. As rightly catched by @JNaftali , it looks like that there are at least 2 bugs to enable SSR with Next.js :
|
@msutkowski It looks like that Node 15 has built-in support for AbortController and AbortSignal : axios/axios#3506 Thus, by using next branch with Node 15, it seems that we finally managed to get SSR working with RTK Query 🎉 : https://github.com/gfortaine/rtk-query-nextjs-example |
I'm closing everything here now. If there's any need for further discussion, that should take place in a new issue over at https://github.com/reduxjs/redux-toolkit/ |
We had a couple questions about "will RTK Query support SSR?"
None of us maintainers have any real experience with SSR. So, requests for more details from the community:
Possibly related docs/issues:
The text was updated successfully, but these errors were encountered: