Why Server Components - A Brief History of Web

Dennis Persson - Jul 9 '23 - - Dev Community

According to Stack Overflow survey 2023, Next.js is now the third most desired web framework, and the sixth most admired one among developers. Even though their old Pages Router is great, they have introduced a new App Router in version 13 of the framework. With that change, they also migrated to use Server Components which can be seen as the next level server rendering.

To understand why Next.js and Server Components are so much more than just traditional client-side React applications, this article will guide you from the beginning of the web, through different types of server rendering with all their benefits, and to the very latest state of web development with Next.js Server Components.

In This Article

From Server Rendering to SPAs and Then Back to Server

To better understand Server Components, we should talk about why they where introduced. For that, we will need a short history lesson.

In the old traditional web programming days, a website consisted of a server rendering HTML code, sending it over network to a client browser which displayed the already rendered HTML code to the user. With that, you had CSS and some small JavaScript files to handle user interactions and navigation to other pages on the website (and also to show a cool visitor counter, which was kind of mandatory).

Newer technologies came and we got smartphones and better computers with increased performance. The web evolved and single page applications (SPA) became the new standard, which basically meant that only one initial webpage where sent to the client. Upon navigating or interacting with the page, REST requests where used to retrieve data from the server and modified the page without actually navigating to another page on the website.

One of the biggest key players for that evolution was the jQuery library, and then the principle kept growing with modern frameworks like React, Angular and Vue which in first place rendered everything on the client side, with support for opting-in to use server side rendering (SSR). As web migrated to SPAs we started to call websites with rich functionalities for web applications.

More and more web application frameworks emerged, and different ways to pre-render web applications arose. Pre-rendering webpages on requests with SSR wasn't enough, frameworks also added support to pre-render pages in build time, so called static site generation (SSG).

We will soon look closer at SSR and SSG, and at the end of the article we will discuss pros and cons of server rendering. But for now, let's forget the why and focus on the what and how.

Where we are today, it's not a question about client rendering or server rendering, we have actually reached a point where we can choose to render small parts of the site differently, and that's where Client Components and Server Components come in.

Client Components vs Server Components

If you have used SPA frameworks like React or Angular, you should be familiar with Client Components already. Client components are basically what a React component really is in nature, a bit of JavaScript code which renders a component on a webpage when it reaches the browser.

Server Components on the other hand, are components rendered into HTML on the server. When I say that, I don't refer to pre-rendering HTML for complete pages with SSR or SSG, I literally mean rendering a single component, like a single React component. It is that granularity Next.js offers with the new App Router system.

To get a better vision of it. Let's look at the old pre-rendering solutions and then come back to Server Components again after that.

Server Side Rendering (SSR) vs Static Site Generation (SSG)

The basics of SSR and SSG are the same, pre-render HTML code on the server and then fetching JavaScript to make the page interactive in a process called hydration. Dependent on framework, navigation can either result in fetching completely new pages from the server or be handled by hydrated JavaScript code as a SPA would do.

Pre-render-and-hydration
Pre-rendering will fetch HTML and then hydrate JavaScript to make the page interactive

When using SSR as a pre-render solution, a complete web page is rendered into HTML on the server at the time when a page is requested. The server will then also fetch the JavaScript and start the hydration process.

Server Side Rendering
Server side rendering renders HTML for a page when a browser requests it

SSG instead allows you to pre-render the application already at build time, which makes it possible to serve the same HTML page quickly to any user without having to render a page in runtime.

Static Site Generation
Static site generation renders HTML at build time and serves it on all requests

While SSG allows to serve instant web pages, it doesn't allow to serve different HTML to different users. All users will get the same content. Dynamic rendering based on user data is not possible with that.

SSG neither allows you to change the HTML by time, since all the HTML code are generated during build time. To solve that issue, Next.js supports something called incremental static regeneration (ISR). What it does, is to allow you to manually, on demand, or periodically regenerate a static generated page.

Although ISR exists in the old Pages Router, it isn't available with the new App Router. It has passed on its baton to Server Components.

Client Components vs Server Components Revisited

So, now when we know about the old options to render pages on the server, we can now go back and look at what Client and Server Components actually are. Look at this picture. It shows you a web page with alternated Client and Server Components.

Client and Server Components
A page can consist of layers of Client and Server Components

As mentioned earlier in the article, Server Components allow you to render individual components purely on the server, while other components on the same page remain as Client Components rendered on the client (with Next.js, they are pre-rendered once on server and hydrated on the client). The picture above, shows and example what that can look like. Green components are rendered into HTML on the server, while the blue ones are rendered with JavaScript on the client.

What this means, is that Next.js now allows to decide for each component whether it should be pre-rendered on the server or if it needs to be rendered on the client. As we will see later, in the Static vs Dynamic Rendering section, Next.js will also automatically determine whether those Server Components need to be rendered at runtime or if they can be rendered in advance at build time.

Server components ripper meme
Ack ack, who's there?

What Rendering Solution Should I Use?

As we have seen, there are a lot of options available for rendering modern web applications. If you use Next.js 13, you have all the options discussed available to choose between. If you start a new project, I recommend using the new App Router system. App Router is production ready, although some other new related features are still in alpha or beta.

To make life easier for you, we will look at which rendering solutions to choose for both Pages Router and new App Router.

Next.js Pages Router

With Pages Router, you have four render options to choose between.

  • Client Side Rendering (CSR)
  • Server Side Rendering (SSR)
  • Static Site Generation (SSG)
  • Incremental Static Regeneration (ISR)

Remember that all these options are per page. The normal behavior is that one application uses most of these options. For example, information pages such as an about us page can usually be generated using SSG, while other pages with more dynamic data would use SSR.

As we will see later in the article, modern web development tend to favor server rendering over client side rendering. CSR is therefore only recommended if there's a need for it. Some cases when it's necessary or recommended to render an application on the client are:

  • When you need to use browser specific Web APIs
  • When you need the user's real location via the geolocation Web API or a IP Geolocation API
  • When your data relies on data stored in the browser, like LocalStorage
  • When apps are communicating in a peer-to-peer network
  • When data changes very frequently, we may want to keep the load on the client not to overload the server

In most other cases, some kind of pre-rendering on the server is recommended. The aim is to cache the generated content as long as possible. If the site is completely static, SSG is usually preferred, if possible.

If you have too many pages to render at build time, or if the static pages need to be updated occasionally, ISR may be an excellent rescue. With ISR, builds can be generated at runtime when you need them, and then regenerated manually or by setting a cache validation time.

If a page isn't completely static, and data isn't only changed by time, but also dependent on authenticated user information or request headers, SSR or CSR is probably what you need to use instead.

Next.js App Router

With the App Router, we don't have as many alternatives. The two main options are Client Component or Server Component.

The concept of static and dynamic rendering is still relevant when we use the App Router, but it's not that prominent. The details about it are a bit complicated, but will be discussed in Static vs Dynamic Rendering section.

The easy part to grasp is when to use Server Components. Just as with the Pages Router, server side rendering is to prefer when using App Router. This means, that we should use Server Components as long as we do not have a reason to use a Client Component. That's also why Next.js defaults to Server Components.

It may sound strange, but the cases of when to use Client Components slightly differs from when to use CSR with the Pages Router. The reason is because the whole underlying structure and design is different with the App Router, so we cannot use the very same rules.

With App Router, you can follow a simple rule. If there is a need to use the browser in any way, or to store some state that may change as a result of user interactions, then you should go for a Client Component, otherwise you can use a Server Component.

More explicitly, a Client Component will be required when:

  • When you need to use browser specific Web APIs
  • When you need the user's real location via the geolocation Web API or a IP Geolocation API
  • When your data relies on data stored in the browser, like LocalStorage
  • The component uses a lifecycle hook or internal state, such as useState, useReducer, useEffect or useContext
  • The component uses a custom hook which uses lifecycle hooks or an internal state
  • The components needs to be interactive, i.e. when it uses onClick or onChange listeners on DOM elements
  • If you are still using React class components for some strange reason

This may seem like a lot, but remember, a great part of a web applications is normally static and are never changed or interacted with. For example, this whole article is static, it's just text and links.

Server Components aren't just build-time generated static pages. Which we will see later, you can still fetch data from APIs in them, even with dynamic none-cached data. You can read cookies and headers, and they allow you to read sensitive data on the server and to avoid sending huge server responses and JavaScript dependencies to the client.

node_modules transportation meme
Don't worry, server has included assembly instructions, Ikea style

Static vs Dynamic Rendering

As revealed earlier, Server Components can be rendered either at build time (static) or at runtime when a request is coming in (dynamic). This is something you should be aware of, but which unfortunately has many ifs and buts.

Next.js have made an attempt to handle this automatically for you. What they do, is to default to static rendering, and automatically enables dynamic rendering when necessary, or when you manually configure that. Dynamic rendering will be activated when:

I'm pretty sure you will have to read those links to understand it, this article is already long enough without that. One thing you should note down though, is that the behavior differs when you use Next.js built in functions for data fetching, cookies, headers and search params, compared to when you use custom solutions or external libs.

Next.js will not be able to know when your component must be dynamic if you don't use their built-in features. In that case, you may have to manually configure the cache behavior.

Pros and Cons With Server Rendering

As promised a few times throughout the article, we will now look at pros and cons of server side rendering. Server side rendering has a lot of benefits and a few disadvantages. The advantages depends slightly on what type of server rendering which is in use, the advantages of SSG can differ from SSR. The pros and cons taken up here is in general terms for server side rendered content.

Advantages of Rendering Data Server Side

  • Servers usually have lower network latency to database than clients, especially if a client's database connection goes through a server
  • A single or a few servers can utilize catches better than thousand or millions of clients
  • Parsing JavaScript is a slow operation and one of the biggest reasons to slow websites. Rendering data on a server reduces the JavaScript bundle size sent to the client
  • Initial page load is faster with server side rendering, which is both good for UX and for SEO
  • Server side rendering is better for SEO overall, due to less JavaScript involvement which historically have had a long and problematic relationship with web crawlers
  • You can use secrets like API keys in server side rendered content. Sensitive information like that shall never be used on a client
  • With server side rendering, you can keep your APIs and databases private and inaccessible on the public internet, since no client depends on it. Without authentication that's more or less impossible to achieve when a client depends on it
  • When data is fetched on a server, there's no need to show loading spinners or skeletons
  • When data is fetched on a server, cumulative layout shift will not be an issue
  • Content rendered on a server is more predictable when an application must support different browsers and devices because the server interprets the JavaScript. On a client, JavaScript can even be disabled

Disadvantages of Rendering Data Server Side

  • Rendering content on servers leads to a higher server load, which in turn leads to more server expenses
  • Servers generally has less support for third-party libraries
  • You don't have access to browser functionality or user's location and device etc. on a server
  • Loading time for navigation may be slow if data isn't reused and each navigation results in a completely new page. This drawback was more of a problem with traditional server side rendering solutions where rendered content rarely was reused

Waldo disappears meme
I wonder where Waldo went?

Server Components Come with Additional Benefits

When it comes to React Server Components, all above listed pros and cons for server side rendering applies, but Server Component also has some additional cool advantages.

One interesting detail is that all Server Components are already pre-rendered when the client receives an application. What this means is that conditionally rendered Server Components can be rendered instantly even when they depend on network data. There's no need to wait for a network response and handle the loading state, even in cases where the component is conditionally rendered in response to a user action.

Reading that, you may be worried about bundle size. I can relieve you by informing that your preoccupations are unwarranted. HTML code does not have a great impact on loading times. HTML is cacheable, way faster to parse and run than JavaScript, and just a fraction of the size of images.

And as the icing on the cake, Server Components add zero bundle size for JavaScript dependencies! Even if you would use a none tree-shacked library at 250 kB, the resulting HTML code would still include 0 kB JavaScript.

Server Components Are Not Perfect

If Server Components are so amazing, are they really perfect? Answers is of course no. There are some potentially inconvenient drawbacks.

Server Components require new knowledge. The migration from old Next.js Pages Router to the new App Router comes with a complete new way of thinking which most developers aren't used to.

Not only the developers' knowledge have become more outdated, the code has so too. Even though newer versions of Next.js is fully compatible with old Pages Router, code using the Pages Router will become legacy code, just as the React's old class components.

Few other drawbacks also exists with code quality. Sharing code has become more difficult when rendering has been split further into client and server rendering. Code marked with use client and import server-only makes it more cumbersome to write DRY code.

I must also admit, although opinionated, the prop drilling of Server Components in Next.js is not the best solution for readability. In Next.js, Server Components cannot be imported in a Client Component, meaning that Server Components rendered in Client Component must be passed as children props and rendered as such. It works, and it's not a new pattern, but might be a bit cumbersome to adapt to.

Where is Waldo
Oh, there you are Waldo, on the server!

Conclusion

Web started out with HTML retrieved from a server with very little use of JavaScript. Time flew by and after many turns back and forth with different rendering solutions such as single page applications (SPA), server side rendering (SSR) and static site generation (SSG), React has now evolved to use Server Components.

Next.js offers a legacy Pages Router which uses the old rendering options together with a very useful incremental static generation (ISR) strategy. With the introduction of Server Components, they now offer a new App Router which allows you to micro-optimize rendering for you web application on a component level with Server Components.

Among all the benefits of Server Components, we have low bundle size, fast page load time, great SEO compatibility and browser support, UX improvements and a possibility to use secrets stored on the server. On top of that, we can get instant rendering for conditionally rendered components without a need to show any loading spinners or skeletons.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .