To SSR or not To
What is it in the context of React (intro for non-techies)
React started out as a client side rendering framework and that's probably the most common case.
So what are the issues with client side rendering?
- It's bad for SEO. There are two different Google crawlers and the one that has JavaScript enabled run less often and Google gives you a penality if you only render client side.
- It takes time to download JavaScript, then parse it in the browser (often underestimated) and then actually execute it and render. (this can be optimized, but will never be as good as directly shipping HTML)
So if we render the page with React already on the server, send it to the client and then let React become active on the client we can tackle both problems.
Great, so why don't we always use SSR with React?
Challanges of SSR
Different environments
On the client you running on a browser engine with the DOM or other APIs like the Web Crypto API available. On the server you running Node which doesn't have a DOM or the Web Crypto API, but instead a rich standard library.
This means every JavaScript library we use must work on both environments e.g. swr, react-hook-form. Most do, but some not and then you either exclude them or only run them on the client e.g. React-Leaflet for rendering maps.
Opinion: Not the biggest issue, but it can cause some headaches now and then.
The client really should render exactly the same as the client
This probably is best explained by a bad example:
Schweizer Illustrierte had the goal to detect the device based on the user agent and ship the right layout. Here an example where it didn’t work out. On the left the SSR rendered layout expecting a larger screen since I used Chrome, but then on the client detecting the actual screen size and switching to probably some mobile/tablet layout.
See the video yourself here how between second 1 to 2 the layout flips: https://www.youtube.com/watch?v=ayRoFkTisiw
Question 1: Should/can we decide on server if we ship mobile, tablet or desktop? Which resolution?
One way is using the user agent. It's tough and based on the Schweizer Illustrierte you can it can cause troubles. Are there any other ways?
In additon this seems to not have future as Chrome want's to remove user agents: https://www.infoq.com/news/2020/03/chrome-phasing-user-agent/
Question 2: Is multi-page setup necessary for different routes?
Maybe I missunderstood, but it was mentioned two times in the call, that we want to do SSR, but keep it in one page.
If we have db.at/haushalt/1
& db.at/haushalt/2
and we ship it as one page we run into an issue: If the user goes db.at/haushalt/1
we can simply ship the first page. The question is what happens when the user goes db.at/haushalt/2
. If we on the server side ship the same page it would jump once client side React becomes active.
Answers
Multi-page setup should be easy to do. Detecting and resolution seems to not possible or very tricky at best. (Happy to be corrected here).
This leaves us with 3 possible architecture choices regarding SSR.
Options
A: SSR and only media queries
We can ship the full mobile and desktop App version in the HTML with SSR as wells in the JS the we serve and only decide base on media queries. On Mobile you basically hide the whole desktop html and on desktop you hide the whole mobile html (using display: none).
<DesktopMediaQuery><DesktopApp></DesktopMediaQuery><MobileMediaQuery><MobileApp></MobileMediaQuery>
This increases the amount of code wit ship. Might still be a viable solution. There are two optimization I know of:
- Only for the client you can code-split the Desktop & MobileApp component and only load what's necessary.
- Don't make a global switch between Mobile & App, but make the switch when necessary e.g. we did this for the navigation at another client: https://ready2order.com/at/. The Navigation is completely different on mobile than on desktop. This certainly increases complexity when developing. If this optimization would lead to an explosion in complexity in your case is hard to judge at the moment.
B: SSR and dedect devices
We try to dedect device based on the user agent and based on that ship the mobile version or desktop version. This can work in theory, but I haven't seen it yet work out well. Why? The issue is that you constantly try play cat & mouse and identify devices.
Still you have to make the mobile version work on small screens and the desktop version on small and big screens, because you constantly miss devices and your ambition will be to still show something nice on them (or force them to do a re-render on the client 😟).
btw Chrome wants to drop the user agent: https://www.infoq.com/news/2020/03/chrome-phasing-user-agent/
C: Client side render
The first visible screen will definitly be slower than on any SSR solution. Usually this is countered with a "good" loading experience using placeholders. The whole flow can be optimized to a certain point and for example Facebook decided for their rewrite to still go without SSR, but try to heavily optimize it. See here https://www.youtube.com/watch?v=KT3XKDBZW7M
The benefit of this solution is lots of flexibily since we can use JavaScript calls to decide what to ship and when. Loading the correct code here would be done via codesplitting.
What might be nice optimization is to pre-render the skeleton of the page that always stays the same. It becomes a bit of a hybrid, but I don't see any issues with this a this point. Just needs some thinking through the loading experience.
D: ?
Any other ideas? Please fill them in here!
Personal Conclusion
Personally I would advice against B, never seen it work. I think A & C are fine and it depends on your goals and tradeoffs. The important part is to understand them and make a concious decision.
Keep in mind while it requires some work, this is not set in stone and can be reworked later on. Happy deciding :)
Join the Newsletter
Thoughts on Software Engineering with a focus on React, Cryptography, CRDTs and Effect.