Client Side Render vs Server Side Render vs Static Site Generation

Differences between these render techniques for web applications

Client Side Render

The server always “serve” only a single HTML file without any content on it, usually just with a <div id="root">. This id="root" was popularized by React, and all the content is loaded by a Javascript file and inserted into the div. So the user has to wait until the browser load, parse and compile the Javascript, before seeing the content and interacting with the page.

With this approach, you will always serve the single HTML file as the entry point, and the client-side javascript will redirect the user to the correct route, using the chosen routing system (eg. React Router, Reach Router…).

Server Side Render

SSR is a technique used a lot in the past, when the server managed to parse and compile all the logic/content, put into a HTML file and “serve” the completed HTML file to the client. So there were no work blocking the browser, since the server already has done all the job. But the problem is, when the user goes to another route, all the previous work is done again, including the browser loading the CSS all again.

But now, with a modern approach to SSR, we can “serve” the first loaded page with SSR, and then the client side Javascript takes over and rehydrate the app. Rehydrate means the javascript will take th static served HTML and turn it into a dynamic DOM that can react to client side changes. This means it attaches eventListeners logic to elements for example, turning the page interactive. When the user change routes, it can all be dealt by a framework in the client side (eg. React router), without the need to hit the server (unless the user reload the page). In order to do this you will need a server with the same code as the client.

The modern javascript frameworks to built UI such as Vue and React have support for building SSR apps. React for example has a method renderToString that makes it possible to render the same components in a HTML string in the server, and then sending them directly to the browser. Then on the browser, the framework rehydrate the markup, turning into a dynamic component. That is why is important the server-side and client-side having the same piece of code!

In the server you cannot use window or document, so you need to take care of that. You can always check if the environment you are in have these properties, or just use them in lifecycle hooks of a framework such as React or Vue.

Be aware that you will need a NodeJS server, capable of absorving the process of SSR if you have high traffic.

SSR compiles your HTML on-the-fly, NOT at build time.

With SSR we have better time to content, because we are serving the full markup page from the server so the user can see it right away.

Static Site Generator

With SSG, we compile and create all files to the pre-selected routes at build time, and store the fully populated HTML as static files in a CDN for example. In order to use static pages, you’ll need a proxy or something to send the user to the right file. So, different from Client Side Routing, you won’t serve the same index.html file everytime. You need to serve the right file for the route the user is trying to access. These files are already built with data, and the client side framework job (like React) is to rehydrate it, attaching the eventListeners for example.

Note that, if you have a giant application with hundred of pages, this is not a scalable approach, since you’ll need to generate a specific html file for each route. For such situation, a SSR approach is better suited.

Using SSG has lots of advantages like:

How NextJS handles pre-rendering

SSG is very performant, but as the rendering is done ahead of time, the served data can be stale when requested by the user. But fortunately there are ways to manage this, without needing to rebuild the app every time. According to NextJS docs, we could use:

Using NextJS, we can create specific configuration for each page of our application. If a page has only static content (eg. an /about page), we don’t need any data fetching, so we can just create the HTML at build time. It is a default for NextJS.

If we have a /products page, with data coming from a database, we can fetch this data at build time, using a method called getStaticProps. This function will be called at build time to fetch external data, and this data will be used to generate the static files. The amazing thing is that getStaticProps just runs on the Node server, so it won’t be included in the client side code, meaning we can safely query our database.

NextJS also cover us in case we need dynamic page generation, like a /products/[id] page. We can use getStaticPaths function to generate these static pages at build time. Then, we could also use getStaticProps passing the dynamic id to fetch the specific data from a database, also at build time.

Now imagining our application has 10000 different product, generate a static page for each product at every build will be VERY slow. Also, if one product is modified, how could we update just that single product’s page, and not all the pages? It can be done with incremental static generation. So how that works:

So basically if a user request a new products page, NextJS instantly serves the fallback page such as a <Loading /> (which is static, that’s why is instantly), and lazily build the requested page at runtime. Once the page is rendered, the next time someone request it, it will be served instantly as well, since it was already built before.

Now what if a product’s data is updated, how can we update it without having to build everything again? By setting a revalidate={{time}} property into a NextJS page, the updated page is pre-rendered again in the background after the time specified, and served right after to the user. While NextJS is pre-rendeing the page again, the user sees the stale page for a while.

These techniques are a mix of SSG and SSR, since we are serving static files right away when user request them, but compiling, building and serving a new page on the fly, just like SSR.

Now for a case when we have a Shopping Cart, we could do SSR to request the page on the fly with the database data. But we could also serve a static page with client side fetching. So basically we:

For fetching in the client, we could use any fetching lib like axios or native fetch API. Vercel has a nice lib called SWR which handles caching, revalidation and more.

In the final toughts, NextJS recommends SSG with client-side fetching or incremental static generation before SSR, since we can leverage the benefits of serving static content AND having dynamic data!

We talked a lot about fetching data to static files in NextJS. But for writing data, we can use API Routes, inside /pages/api directory. Won’t talk a lot about this here, but it creates and API endpoint for you inside your NextJS project. Then you can handle requests to it, and it only works on server side, so it won’t increase your client side bundle. It can be used as BFF, like and API Gateway for hitting other endpoint sources! Very nice.

Interesting Points