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
ordocument
, 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:
-
Very fast and predictable, since HTML files can be cached and served through a CDN.
-
Even if your data source goes down, user will always see the static content
-
Minimizes the server load, since we don’t need to hit the server to generate a new page on the fly in each request (files are served through the CDN)
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:
-
Incremental Static Generation: we can add and update pages after build time.
-
Client Side Fetching: generate static pages without data at build time, and fetch the data in the client.
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:
-
We don’t generate all pages ahead of time
-
When user requests a page for
/products/X
, NextJS serve a loading indicator and in the background starts to render the page (insideNodeJS
). -
When done, it serves the rendered page for
/products/X
. -
Next time someone requests this same product, the pre-rendered page will be served instantly, just like a regular SSG.
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:
-
Serve the pre-rendered page without any data, just a loading state.
-
Fetch and display the shopping cart products at the client side.
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
- Chrome and Bing have the ability to index synchronous Javascript. That means if you are serving all you content through a javascript bundle that will load in the client, it has to be synchronous for the SEO indexation happens. If you are loading the Javascript bundle in an async way, you may need SSR for better SEO.