[NoSsr] Investigate better alternatives in React 18
Summary 💡
Check each use case with React 18 in mind. Our docs are probably a good start.
Examples 🌈
<NoSsr fallback="loading">
<Suspense fallback="loading">
<SomeLazyComponent />
</Suspense>
</NoSsr>
can be written without NoSsr alltogether.
Motivation 🔦
NoSsr only renders its children when an effect ran. This leads to cascading updates which is especially problematic for the default case where defer={false} and we use layout effects.
The reason we have NoSsr is to not render components that don't do anything before hydration. In React 18 we now have access to Suspense which enables streaming rendering and selective hydration which should cover most use cases while also improving existing scenarios (e.g. prioritizing hydration).
Search keywords:
The reason we have NoSsr
To extend on the different product use cases we have had so far for the NoSsr component, I have seen:
- Speed up server-side rendering (reduce CPU load on the server), e.g. DDoS, need to skip the whole phase
- Don't render broken third-party component that uses the browser API (likely a less frequent issue nowadays), e.g. styled-components not configured in v4, react-admin
- Speed up to interactive for the most important parts of the user / make it feel more responsive. How? By splitting hydration into two steps. e.g. demo's toolbar, NProgressBar.
- Hide elements that can't be cached in the SSR output, e.g. user login information.
- Dodge hydration inconsistencies, e.g. Intl.NumberFormat using different locale datasets between client and server
Suspense seems to cover 3.
Suspsense covers 1, 3, 4. 2 does not need an extra component. 5 is an issue with the SSR setup.
Speed up server-side rendering (reduce CPU load on the server), e.g. DDoS, need to skip the whole phase
Not sure what the type of attack has to do with it. If you're DDoSed no amount of NoSSR will help. The attack preventation should not happen at the render layer.
- Do you mean that the solution should be to create its own component?
- I guess it depends on the approach. When using intl.datetimeformat, the dataset https://nodejs.org/api/intl.html#intl_internationalization_support is evolving. From what I understand, it's not guaranteed that the browser uses the same version of the dataset as the client. But I guess developers can consider these hydration mismatches and hedge cases, it a small flicker. Or developers can use a polyfill with predictable behavior, or they can render an empty placeholder on the server-side.
Not sure what the type of attack has to do with it. If you're DDoSed no amount of NoSSR will help. The attack preventation should not happen at the render layer.
- It might be a hedge case to ignore. In the past, I had wrapped the whole server-side output in Next.js with a NoSsr, wired it to a value in Redis, to be able to completely turn off server-side rendering in case we had a CPU load issue (not enough resources/too much traffic).
So, the path we might be able to take is to deprecate NoSsr?
So, the path we might be able to take is to deprecate NoSsr?
We'll want to wait for a stable release of 18 anyway. And then we can go through each use case of NoSsr and see if we can replace it.
to be able to completely turn off server-side rendering in case we had a CPU load issue (not enough resources/too much traffic).
Yeah, this should happen at another layer. It's not even clear this works since you would have to signal to the client side bundle that the rendered tree changes. Otherwise you're hydrating differnt trees which generally has undefined behavior. If it does work then you're degrading client side rendering as well. You should just send the HTML skeleton and then call ReactDOM.render on the client. Instead of calling sending the ReactDOMServer.render(null) and hydrating that tree.
I'm currently need suspense at my top-level component. In my case, it's next.js apps
<Suspense fallback={<div>Loadingss...</div>}>
<DefaultLayout>
<Component {...pageProps} />
</DefaultLayout>
</Suspense>
but in order for this to work, I need to wrap it with <NoSsr/> component
<NoSsr fallback="Loading">
<Suspense fallback="Loading">
<...Rest/>
</Suspense>
</NoSsr>
Is this the correct approach for now, in order to make the suspense of React 18 work?
I'm afraid this might slow down the TTI since we pass fallback two times in there. This is just my guess tho
Is this the correct approach for now, in order to make the suspense of React 18 work?
In React 18 you can use <Suspense /> on the server. It's one of the reasons why I wanted to remove this component since it most likely results in degraded UX.
This fix will be available in the next npm release of Base UI.
In the meantime, you can try it out on our Canary release channel:
npm i https://pkg.pr.new/@base-ui-components/react@3398