Server Side Rendering means that your Javascript produces an HTML string that's sent over the wire, like "<div><button></div>". The browser can render this HTML very, very fast, which is why it's desirable for performance. However, the <button> that's sent to the client won't have any Javascript onClick interactivity. That's where "hydration" comes in, React (or whatever) re-runs over the HTML already in the browser, and wires up click handlers, etc. The HTML on the page won't change, but now it will be interactive. The benefit is the user gets to see the HTML much faster.
For some libraries that are very large, like Threejs or a charting library, or any tech that draws to a Canvas which probably can't be SSR'd, it's probably best to detect if you're on the server or the client in your React code (which is usually done by injecting some env variable into your bundle), and render an empty <div> on the server, and when the client hydrates, it will download the large library and create the chart.
For other data, the trick is to create an API that works on both the server and the client. So `getPosts()` on the server will call to your internal API, and when that same function is executed on the client, it calls out to GraphQL. Then you switch out your API client on the server vs client bundle. In this way, your app is "universal," you can do a blocking fetch of posts on the server and render server side HTML, and on the client you can still call `getPosts()` on button clicks or whatever to fetch new posts. And API calls are much faster on your server side because you're in the same VPC, they don't go out over the public internet, and you might not even need TLS for them.
For most SSR apps with hydration, the entire app is downloaded again on the front-end. What I want to see is selective hydration first. For example, you define your entire app in React, and things that don't change, like the header and the footer, are only rendered on the server, and the JS React code is never sent to the client. For things that need interactivity, like dropdowns, the React code is sent to the client only needed for the dropdown to make it interactive. I think selective/partial hydration should be the default behavior for webapps, only deviating if you have things like large JS libraries as mentioned above, or maybe applications that you always keep open in a single tab, like an email client.
These same principles apply to CSS rendering too. CSS should always be extracted to a static stylesheet and sent as a <style> tag. If you have to download JS, parse JS, run JS, and then get a stylesheet injected into the DOM, it's also a bad performance hit. Browsers will always be faster than us at showing vanilla CSS and HTML to users as fast as possible, which is what we want. The most important consideration of CSS libraries is whether or not they can produce static stylesheets. It's mystifying to me that none of the CSS-in-JS libraries mention if they can/can't do this as part of their landing pages. If users have to execute JS before CSS starts styling the page, you've lost the performance game.