Don't SSR private routes!


Martin Koparanov

September 13, 2019

Server-side rendering in React is quite hard as it is. You have to plan and develop your whole application with SSR in mind, and you still can have a rough time setting it up. SSR is great, but it is still not quite there yet. Sometimes, it is just not worth it, and it may even be dangerous!

Benefits

Let's first take a look at why you might want to have SSR. If you are familiar with SSR, you can skip this section and learn more about its pitfalls.

  • SEO

    The most beneficial reason to have SSR is because of SEO (Search Engine Optimization). In case you are not aware, when you serve a React application, you are serving index.html with an empty body and a script that requires the actual React bundle. When the React bundle is loaded, JavaScript kicks in and builds the page client-side. This is great and all, but what if a search engine's crawler decides to take a look? It doesn't understand your JavaScript bundle, it will just see an empty HTML and will decide that the page is empty (Google parses and runs the JavaScript, but many don't). This is bad for your SEO.

    On the other hand, when using SSR, when you make a request the built HTML will be served together with the JavaScript bundle. The same code which runs on the client to build the page will be run on the server first to generate the HTML and send it to the client. Now, when a crawler takes a look at your page, it won't see an empty page - it will see your actual content!

  • Perceived speed

    When you load your React application, the browser has to take a few steps before it runs the JavaScript bundle. First of all, it obviously has to download all the files. If you serve your static files gzipped (if not, you should), the browser first has to unzip them. When they are unzipped, the browser has to parse and JIT compile your JavaScript. After all that React can start building your page. This takes some time. In the case of SSR, all of the above is still true, but it is less noticeable. While the browser does all that, you already have HTML to look at. When that is done, JavaScript kicks in and takes control.

  • Works without JS

    If the client has JavaScript disabled, your page will still work (kind of). It will work, but won't be interactive at all. But it is still better than nothing!

Pitfalls

All of that is great, but you have to consider if all the hassle to achieve this is worth it because as I said - server-side rendering is hard. Here are some pitfalls to consider:

  • Touching window object

    You have to avoid touching the window object because it is not available on the server and if you try to get a property, your rendering may crash. (Note: you can still use it, but make sure it is in a mounted component). Okay, easy enough. But what if a library you are using is breaking this rule? You have to deal with this now.

  • Rendering content based on resolution

    Imagine you have a mobile and desktop version of a component and you decide you will switch between the two based on the client's resolution. Okay, but while rendering on the server, you have no idea what resolution the client has. Let's say you always render the mobile version - but what if the user is browsing on a desktop? This can lead to some serious issues, because when the client loads the SSR-ed page, and it doesn't match with what the client-side script would have rendered, you get desynchronization between the expected and actual DOM tree. There are some workarounds, such as rendering content based on the user agent while SSR-ing, but it is far from perfect. You can easily break this logic by loading the page on desktop and shrinking the viewport. The easiest and safest way is to just use CSS if you intend to have SSR.

  • Cache

    You may be thinking "this is not so bad, I can live with this". Well, hold on, it gets more interesting! If you are doing SSR, you probably will want to employ some sort of caching technique - and rightfully so - running servers is expensive! But now, you have to keep a separate cache for every user and you have to be very careful with this. There are a ton of ways this can go wrong! You have to make sure you are always serving the correct person a correct cached response. Otherwise, you may serve someone else's personalized page, which may break the user experience, and more importantly - you can expose your users' personal data to other users. There are a couple of ways I can think of off the top of my head in which this can go very wrong. Don't believe me? A giant like Steam had this problem - imagine opening your account settings and seeing someone else's account. You can learn more about this accident.

  • Concurrency

    If you are doing SSR, you will most likely be doing it in NodeJS. Now, as most people know, JavaScript (and inherently NodeJS) is single-threaded. Doing asynchronous stuff still happens on one thread. This is mostly not an issue, but imagine that you have some global object. If you or a library you are using is writing to it you may have a problem. Imagine that two people want to load a page. What will happen is that your server will start rendering the page for the first user. While it is still rendering it, another user wants to open the page. Now you have started rendering the page for the second user before the first render has finished. While rendering the second user's page, something mutates this global object. But in the meantime, the first render continues and it reads said global object. It will have the wrong data. Now, imagine that this happens with a couple of hundred users. Yikes!

Trade-off

I can offer you a simple trade-off. You may have guessed it already (it's in the title, duh) - do SSR just for the pages that matter. You can SSR only your public pages. Those are the pages that really need good SEO, your private routes wouldn't (or shouldn't) be accessible by crawlers anyway, and the perceived loading speed difference is not going to be huge (hopefully!). You can save yourself all the hassle, time, your company's money, and your sanity by just doing SSR on public pages only! The risks to mess something up are much lower - you can even cache for much longer periods. After all, you don't expect your public page to change that often. To me, that seems like a pretty good trade-off.

"Partial" SSR

Keep in mind that you can still do SSR partially. For example, if you have your user's account displayed in the header, you can SSR it without the user's account and add it on the client when the information is loaded.

Conclusion

I hope you now see what I mean when I say that SSR is hard. The gist of it is quite easy, but you can come across some unexpected problems, some of which can be quite dangerous. Keep in mind that these are all my opinions and experiences, in the end, what matters is that you do whatever works for you. Just remember to be careful and consider the points mentioned above!


Categories: Backend, Frontend,

Tags: react, ssr,