If you have any thoughts on my blog or articles and you want to let me know, you can either post a comment below(public) or tell me via this feedback form

Exploring Various SSR (Server-side rendering) from a Historical Perspective

Did you know that when you discuss SSR with your friends, it’s highly likely that your understanding of SSR differs? Let’s take a few scenarios. Which ones do you consider as SSR?

  1. Generating the view from the backend using PHP.
  2. Frontend is a React-based SPA, but if the backend detects a search engine, it switches to a different template that is specifically designed for search engines instead of the React-rendered page.
  3. Frontend is a React-based SPA, but it uses Prerender to render the page as HTML and then serves it to the search engine (regular users still get the SPA experience). The difference from the previous scenario is that the view seen by users and search engines is mostly the same.
  4. Frontend is a React-based SPA, and the backend uses renderToString to render React into a string, but there is no data. The data is fetched on the frontend.
  5. Frontend is a React-based SPA, and the backend makes API calls to fetch data for each page. After fetching the data, it calls renderToString to output HTML. On the client-side, hydration is performed to make the page interactive.

Some people believe that any view generated by the backend is considered SSR, so all scenarios 1 to 5 are SSR. Others think that the frontend must be an SPA for it to be called SSR, so scenarios 2 to 5 are SSR. Yet, some people consider hydration as the key aspect of SSR, so only scenario 5 (or 45) is SSR.

Why this article?

Five years ago, I wrote an article discussing SPA and SSR: Understanding Technical Terms with Xiao Ming: MVC, SPA, and SSR. My thoughts back then align with my current ones.

By “current me,” I mean that I haven’t fully organized my thoughts yet. I’m writing this preface, and the content below is still unfinished. I will share the thoughts of “future me” at the end. However, for now, my current perspective is that “not all ways of generating views from the server can be ‘appropriately’ called SSR.”

Let’s consider a hypothetical scenario:

A: Hey, how do you render your company’s web pages?
B: We use SSR.
A: Oh, so what framework do you use for SSR?
B: Just plain PHP, no framework. The frontend is built with jQuery.

Now, another example:

A: Dealing with SSR issues has been quite frustrating lately. It’s challenging to handle the data.
B: It’s been fine for us. We’ve been using PHP without any major issues.

Although the term “server-side rendering” literally means rendering by the server, so there’s no problem in calling PHP SSR based on its literal meaning, I believe the key question is “Why do we need the term SSR?”

My understanding is that in the era before SPA became popular, there wasn’t much that fell under CSR (Client-side rendering), so there was no need for the term SSR. At that time, you would simply say, “We use PHP” rather than saying, “We use PHP for SSR.”

It’s somewhat similar to when I ask my friend how much his lunchbox costs, and he replies, “100 bucks” instead of “100 New Taiwan Dollars” because we assume the currency is New Taiwan Dollars, so there’s no need to explicitly mention it. Similarly, back then, there was only one path of rendering from the server, so there was no need to specifically mention SSR.

However, with the rise of SPAs, many things started to shift towards CSR. This led to problems that only CSR encounters, such as SEO. In such cases, certain aspects need to be handled by the server to solve these problems. In this context, the term “Server-side rendering” took on a new meaning, becoming a “server-side solution to address CSR issues.”

Therefore, calling PHP SSR is not entirely accurate; it lacks meaning.

It’s like if we define “beverage” as “a drinkable liquid,” can you say that hot and sour soup is also a type of beverage? Technically, there’s no issue with the definition, but when someone asks you, “What’s your favorite beverage?” would you say hot and sour soup? Probably not. Similarly, we don’t refer to hot and sour soup as a beverage.

Likewise, although SSR literally means that, PHP, which is a traditional server-side content rendering solution, can be called SSR, but you wouldn’t refer to it that way. SSR is more suitable to refer to a “server-side solution used to address SPA issues.”

I started to become curious at this point. Was SSR really not commonly used before the popularity of SPA and CSR? If so, when did it start? Also, my understanding of SSR basically started with React. So, did earlier frameworks like Angular, Ember, or even Backbone not have this issue? If they did, what were their solutions called?

So, I embarked on a journey of exploration that would take a lot of time, perhaps discussing issues that may not be so important, but I enjoyed the process.

When did SPA become popular?

As mentioned earlier, my argument is that the term “SSR” started to become popular after the rise of SPA, specifically referring to server-side solutions for handling CSR and SPA issues.

I believe that the development of SPA is closely related to the overall development of frontend web technologies. So, let’s take a look back at history!

JavaScript was officially introduced in 1995. Although JavaScript’s functionality was not as mature at the time, there were already other technologies that could run an application on a webpage, such as Java Applets.

Flash was released in 1996. In the early days when JavaScript was not as powerful, Java Applets or Flash were used to create more complete web applications.

So, when did JavaScript mature enough to stand on its own and be used to write a web application? The answer is related to technological advancements. As a web application that needs to communicate with the backend, what is most needed?

It is something that now exists as naturally as air and water: XMLHttpRequest.

To be able to operate independently and communicate with the server without page reloads, XMLHttpRequest is a necessary condition. We needed the XMLHttpRequest API to exchange data with the server without page reloads.

However, in the beginning, not all browsers used XMLHttpRequest. Microsoft, which had the concept first, used ActiveXObject. This can be verified from the first version of jQuery’s source code from 2006:

// If IE is used, create a wrapper for the XMLHttpRequest object
if ( jQuery.browser.msie && typeof XMLHttpRequest == "undefined" )
  XMLHttpRequest = function(){
    return new ActiveXObject(
      navigator.userAgent.indexOf("MSIE 5") >= 0 ?
        "Microsoft.XMLHTTP" : "Msxml2.XMLHTTP"
      );
  };

After mentioning XMLHttpRequest, it is natural to talk about Ajax. The term “Ajax” comes from an article published by Jesse James Garrett on February 18, 2005, titled Ajax: A New Approach to Web Applications. It describes a new communication pattern using HTML, CSS, DOM, and XMLHttpRequest, which I believe is the prototype of SPA.

ajax

(Image from the mentioned article)

In the article, there is also a mention of the difference between XMLHttpRequest and Ajax:

Q. Is Ajax just another name for XMLHttpRequest?
A. No. XMLHttpRequest is only part of the Ajax equation. XMLHttpRequest is the technical component that makes the asynchronous server communication possible; Ajax is our name for the overall approach described in the article, which relies not only on XMLHttpRequest but on CSS, DOM, and other technologies.

From historical data, it seems that Microsoft Outlook was the earliest product to mention and utilize these technologies, starting in 2000. However, in terms of widespread use and popularization of the term, it belongs to Google around 2004-2005.

Around this time, the JavaScript ecosystem also experienced vigorous development. Many libraries emerged, such as Prototype, Dojo Toolkit, MooTools, and the still-existing jQuery, which further advanced frontend web development. In 2006, YUI (Yahoo! User Interface Library) was born, and Ext JS, a framework specifically designed for web applications, appeared in 2007.

Although these libraries make web development easier, SPA did not become popular until the birth of two pioneers.

On October 13, 2010, Backbone.js released its first version, followed by the initial release of AngularJS a week later on October 20.

A year later, other SPA frontend frameworks emerged. Ember.js was released on December 8, 2011, and Meteor.js appeared on January 20, 2012.

Typically, it takes at least six months to a year for a new framework to become popular. Therefore, I believe that 2011 and 2012 marked the beginning of the rise of SPA. But how can we support this claim?

Keyword search trends to some extent represent the popularity of certain technical terms at the time. From the graph below, we can see that the term “SPA” started to climb around 2011 and 2012, which aligns with my speculation (although this data may not be very accurate, I couldn’t think of a better source at the moment):

SPA Search Trend

(As for the peak in 2004 and 2005, I don’t know, but I’m curious to find out. Maybe it’s related to the popularity of various Google services? If anyone has any clues, feel free to message or comment to discuss.)

The rest of the story is more familiar. React was officially released in May 2013, followed by Vue in February 2014. With the rise of frontend frameworks, SPA became increasingly popular and eventually became the mainstream approach to frontend development today.

How did early SPAs solve the CSR problem?

From the history of development mentioned above, it is clear that Backbone.js and AngularJS were the pioneers that ushered in the era of SPAs. But how did they solve the CSR problem, such as SEO?

Let’s start with AngularJS. I found a project on GitHub from 2013: angular-on-server. In the project’s wiki introduction, it states:

We need to pre-render pages on the server for Google to index. We don’t want to have to repeat ourselves on the back end. I found a few examples of server-side rendering for Backbone applications, but none showing how to do it with AngularJS. To make this work I have modified a couple of Node modules, jsdom and xmlhttprequest. They are loaded from local subdirectories (/jsdom-with-xmlhttprequest and /xmlhttprequest).

If this is true, it means that AngularJS had limited SSR solutions at that time, with most solutions being based on Backbone.js.

Based on the information I found, it seems to be the case. For example, this question from 2013: AngularJS - server-side rendering. From the answers, it is evident that there were indeed limited solutions available.

AngularJS officially supported SSR only in late June 2015, as mentioned in this presentation: Angular 2 Server Rendering. A few days after the presentation, they open-sourced Universal Angular 2, which is the predecessor of Angular Universal.

In the README at that time, it stated:

Universal (isomorphic) JavaScript support for Angular 2

The term “isomorphic” should bring back memories for many people, but we’ll discuss that later. Now, let’s see how Backbone.js solved the SPA problem.

I found an ancient example on GitHub from 2011: Backbone-With-Server-Side-Rendering. The README states:

Backbone.js is a great tool for organizing your javascript code into models, collections, and views, without tying your data to the DOM elements. However, most tutorials show how to render the HTML only via Backbone (client-side), which means that none of your content is crawled by search engines. This is possibly a major problem if you’re not making an app hidden behind an authentication system.

The unique thing about this project is that SSR (Server-Side Rendering) is implemented through Ruby on Rails. However, after examining the source code, it seems more like an experimental project. The HTML is outputted by the backend and then handled by Backbone.js on the frontend. It is a simple example rather than a complete demo.

If you want a more complete solution, the one to consider is Rendr, which was open-sourced by Airbnb in 2013.

On January 30, 2013, Airbnb’s tech blog published a new article titled Our First Node.js App: Backbone on the Client and Server. It discusses the issues with Single Page Applications (SPAs) and the challenge of integrating logic on both the frontend and backend. The ultimate solution presented in the article is the Rendr package, which allows executing Backbone.js on the server.

The open-sourcing of Rendr was announced in an article three months later, titled We’ve open sourced Rendr: Run your Backbone.js apps in the browser and Node.js. It states:

Many developers shared the same pain points with the traditional client-side MVC approach: poor pageload performance, lack of SEO, duplication of application logic, and context switching between languages.

This indicates that many developers at that time were aware of the issues with SPAs and were seeking a more comprehensive solution.

To execute Backbone.js on the server, a prerequisite is that the server must be able to run JavaScript.

Node.js was released in 2009, and Express came out at the end of 2010, while NPM was introduced in 2011. By mid-2012, Node.js was still at version v0.8.0, which was an early stage. Looking back now, Node.js started to be widely used around 2012-2013.

In summary, based on the information I found, the library that was perhaps widely used for SSR earliest was Rendr, introduced in 2013. It can achieve the following: “rendering on the server-side initially, but then taken over by JavaScript on the client-side,” as mentioned in Airbnb’s article:

Your great new product can run on both sides of the wire, serving up real HTML on first pageload, but then kicking off a client-side JavaScript app. In other words, the Holy Grail.

The image below illustrates the so-called Holy Grail, taken from Airbnb’s original article:

holy grail

When it comes to this point, let’s organize the timeline and my personal speculation.

Since the release of Backbone.js at the end of 2010, SPA (Single Page Application) has gradually become popular, and people have also realized the problems encountered in front-end rendering. Therefore, they started to implement different solutions, which is server-side rendering.

Backbone.js continued until 2013 when Airbnb open-sourced Rendr, finally providing an ideal solution: “initial rendering on the server-side, and subsequent rendering on the client-side, with both client and server sharing the same codebase.”

The concept of “running the same code on both the client and the server” is what was mentioned earlier as isomorphic.

By the way, Ember.js’s official SSR (Server-Side Rendering) solution should be this one at the end of 2014: Inside FastBoot: The Road to Server-Side Rendering

One more thing to mention, according to the article The History of React.js on a Timeline, FaxJS is the predecessor of React, and when it was open-sourced at the end of 2011, it already had an API for server-side rendering, which could render components into static HTML and reattach events on the client-side: https://github.com/jordwalke/FaxJs/tree/5962e3a7268fc4fe0251631ec9d874f0c0f52b66#optional-server-side-rendering

Isomorphic JavaScript

The term “Isomorphic JavaScript” comes from an article published by Charlie Robbins on October 18, 2011: Scaling Isomorphic Javascript Code

The article defines Isomorphic as follows:

Javascript is now an isomorphic language. By isomorphic we mean that any given line of code (with notable exceptions) can execute both on the client and the server.

More details can be found in an article published by Airbnb on November 12, 2013: Isomorphic JavaScript: The Future of Web Apps

The article also includes a practical example that is worth referring to: isomorphic-tutorial.

In addition, the article mentions three predecessors of Isomorphic JavaScript before Rendr. One of them is Mojito, open-sourced by Yahoo! in 2012. The article mentions a beautiful imagination:

Imagine a framework where the first page-load was always rendered server-side, and desktop browsers subsequently just made calls to API endpoints returning JSON or XML, and the client only rendered the changed portions of the page.

It is basically the current mainstream way of working in front-end development.

Another one is Meteor.js, and the third one is Asana’s Luna. Luna is quite interesting, and upon closer inspection, its syntax has a bit of a React flavor.

The term “Isomorphic” was gradually replaced by “Universal” after Michael Jackson’s article in 2015: Universal JavaScript.

This article mainly suggests that “Universal” better expresses the original intention and is easier for the audience to understand. Therefore, it advocates using “Universal JavaScript” instead of “Isomorphic JavaScript”.

Mid-summary

Up to this point, I have answered a few of my previous questions:

Q: Was the term SSR really not used much before the popularity of SPA and CSR? If so, when did it start?

I’m not sure because I didn’t specifically search for earlier evidence. However, if we look at the search trend for the term SSR, it started to take off around 2012-2013, which is around the same time as the popularity of SPA.

SSR Search Trend

Q: My understanding of SSR basically started with React. Does that mean earlier frameworks like Angular, Ember, or even Backbone didn’t have this issue? If they did, what was their solution called?

They had the same issue, and the solution was also called SSR.

To be honest, discussing the precise definition of the term SSR doesn’t have much significance. It can be a bit nitpicky, and it’s difficult to reach a conclusion or convince others that “this definition is correct.” The key is to ensure that both parties have a consistent understanding during communication.

When talking about SSR, many people only focus on the SEO aspect, but if we think a bit more carefully, SSR is needed for more than just SEO.

Problems SSR Aims to Solve

SSR aims to solve the problems caused by CSR, including:

  1. SEO
  2. Link previews on various social platforms
  3. Performance
  4. User experience

If CSR is used, since the UI is generated through JavaScript, search engines will only crawl blank HTML. Even if Google executes JavaScript, other search engines may not. Even if all search engines execute JavaScript, you can’t guarantee that the crawled results will be what you want.

For example, it’s difficult to know when they will finish executing JavaScript. If the API for fetching data takes two seconds to respond, and the search engine only waits for one second after executing JavaScript, the result will still be empty.

Link previews on social platforms are another problem. The <meta> tags generated on the client side are not useful. Usually, the bots of these social platforms don’t execute JavaScript; they only look at the response. Therefore, the <meta> tags on CSR pages can only be the same and cannot dynamically determine the content based on different pages.

The third and fourth points can be considered together. Although modern devices generally run fast and can execute JavaScript quickly, in cases where the JavaScript is large and the device is older, executing JavaScript still takes some time.

When will the user see the UI of a CSR page? They need to download the JavaScript first, and after downloading, it needs to be executed. After executing and updating the DOM, the user can see the complete UI. During the waiting period, the screen is blank. Although some websites show a loading indicator, the overall user experience is not very good.

If we can get the UI in the initial response, the user experience will be better, and performance will increase. Even on older devices, users can see the UI from the beginning without waiting for JavaScript to finish executing.

Various Types of SSR

Originally, I only wanted to write this paragraph, but unexpectedly, it turned into a historical archaeology of front-end development.

In response to the problems caused by CSR mentioned earlier, various solutions have emerged. Each solution is different, and not all problems can be solved at once.

Type 1: Rendering a different template for search engines and bots

This solution only solves the problems of SEO and link previews. When the server receives a request from a search engine or a bot from a social platform, it directly uses the original backend template to output the result.

Like this:

const express = require('express');
const app = express();

app.get('/games/:id', (req, res) => {
  const userAgent = req.headers['user-agent'];
  
  // Check user agent
  if (userAgent.includes('Googlebot')) {
    // render the SEO template for the bot
    const game = API.getGame(req.params.id);
    res.send(`
      <html>
        <head>
          <title>${game.title}</title>
          <meta name="description" content="${game.desc}">
        </head>
          <body>
            <h1>${game.title}</h1>
            <p>${game.desc}</p>
          </body>
        </html>
    `);
  } else {
    // return index.html for regular users
    res.sendFile(__dirname + '/public/index.html');
  }
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

For regular users, the issues of performance and user experience are still not resolved. This solution only addresses SEO and link preview, ensuring that the web page captured by these bots has data.

I have implemented this approach in my work, and the advantages are that it is simple, fast, and does not interfere with SPA. The disadvantage is that the page seen by the Google bot may be different from what the user sees, which could potentially affect SEO scores. After all, outputting special pages for the Google bot is considered an anti-pattern called cloaking, as mentioned in Google’s official video: Can we serve Googlebot a different page with no ads?. It is recommended to have the exact same page.

However, compared to not showing anything to the Google bot, this solution is still better.

Second Approach: Pre-rendering for Search Engines

The most well-known framework for this approach is Prerender. In simple terms, it uses a headless browser like Puppeteer on the server-side to open your page and execute JavaScript, then saves the result as HTML.

When the search engine requests data, this HTML is served, so both users and bots see the same content.

I tried it locally and created a simple page using create-react-app:

import logo from './logo.svg';
import './App.css';
import { useState, useEffect } from 'react'

function App() {
  console.log('render')
  const [data, setData] = useState([]);

  useEffect(() => {
    document.querySelector('title').textContent = 'I am new title' 
    fetch('https://cat-fact.herokuapp.com/facts/').then(res => res.json())
      .then(a => {
        setData(a);
      })
  }, [])

  function test() {
    alert('click')
  }
  
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        {data && data.map(item => (
          <div>{item.text}</div>
        ))}
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
          Can you see me now?
        </a>
        <button onClick={test}>hello</button>
      </header>
    </div>
  );
}

export default App;

The main points I wanted to test were:

  1. Whether the page is still interactive.
  2. Whether dynamically modified titles are reflected in the results.
  3. Whether the output includes the results obtained from an API response.

After prerendering, the output HTML is:


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="icon" href="http://localhost:5555/favicon.ico">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="theme-color" content="#000000">
    <meta name="description" content="Web site created using create-react-app">
    <link rel="apple-touch-icon" href="http://localhost:5555/logo192.png">
    <link rel="manifest" href="http://localhost:5555/manifest.json">
    <title>I am new title</title>
    <script defer="defer" src="http://localhost:5555/static/js/main.21981749.js"></script>
    <link href="http://localhost:5555/static/css/main.f855e6bc.css" rel="stylesheet">
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root">
      <div class="App">
        <header class="App-header">
          <img src="/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg" class="App-logo" alt="logo">
          <div>When asked if her husband had any hobbies, Mary Todd Lincoln is said to have replied "cats."</div>
          <div>Cats make about 100 different sounds. Dogs make only about 10.</div>
          <div>Owning a cat can reduce the risk of stroke and heart attack by a third.</div>
          <div>Most cats are lactose intolerant, and milk can cause painful stomach cramps and diarrhea. It's best to forego the milk and just give your cat the standard: clean, cool drinking water.</div>
          <div>It was illegal to slay cats in ancient Egypt, in large part because they provided the great service of controlling the rat population.</div>
          <a class="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">Learn React Can you see me now?</a>
          <button>hello</button>
        </header>
      </div>
    </div>
  </body>
</html>

The title has changed, and the content is the result of executing useEffect() and rendering after the fetch operation. Clicking the button can also trigger events, so there don’t seem to be any issues.

If we take a closer look, the rendering process of the prerendered page is similar to a normal React app. The only difference is that the original HTML already contains content, but React still executes once and re-renders the entire page.

Therefore, the following situation occurs:

  1. The server response is a complete page with data.
  2. React starts and performs the initial rendering, at which point the data becomes the initial state, and the page becomes a state without data.
  3. React mounts the result to the DOM, triggers useEffect, and makes another API call to fetch data.
  4. The state is updated, and a page with data is rendered.

This approach still targets search engines only. The difference from the first approach is that the page seen by users and search engines will be more similar, but still not exactly the same. After all, regular users will see a page with no content.

Can we show the prerendered page to regular users?

Yes, it is possible, but it may be a bit strange if there is an API involved. As mentioned earlier, the initial state has no data, but the HTML does. Therefore, the page seen by users will be: Has data (due to prerendered HTML) => No data (state initialization) => Has data (API call on the client). This may not provide a good user experience, so it is usually not done.

The advantage of this approach is that it is convenient. It does not require modifying the original SPA; only a middleware needs to be added on the server-side. However, the implementation is more complex compared to the first approach, and there are many details to consider.

Third Type: Server Rendering Client App

This is the type that has been mentioned before: “generating the initial HTML on the server and handing over subsequent operations to the client.” Compared to the previous two types, this is the more ideal SSR and is commonly known as Isomorphic/Universal.

This approach not only solves the SEO problem but also addresses the user experience. When a user visits the website, they can immediately see the rendered result. However, at this point, the page may not be interactive because the JavaScript has not finished executing. It is necessary to wait for the JavaScript to complete execution and attach event handlers before the page can be truly interactive.

Additionally, since the initial page has already been rendered on the server, there is usually no need to modify the DOM again on the client side. Only attaching the event handlers is required, and this process is called hydration.

I find this term quite visually descriptive. Imagine that the page output by SSR is “dehydrated,” very flat and dry, with only the visual elements. It cannot be interacted with. When it reaches the client, it needs to inject water into this dry page, add event handlers, and bring the whole page to life, making it interactive.

However, the drawback of this solution is that it is more complex to implement. One needs to consider API-related issues. For example, if an API call is placed inside a useEffect, it cannot be executed during server rendering, resulting in a page without any data.

Therefore, it may be necessary to add a function to fetch data for each page, store it in props, and correctly output a page with data during server-side rendering.

Due to its complexity, this task is usually delegated to frameworks like Next.js, which adopts the approach I mentioned earlier (Pages Router) and adds a getServerSideProps function to the page.

By the way, the first version of Next.js was released on October 25, 2016.

Fourth Type: Render at Build Time

This is a specialized form of SSR tailored for specific product scenarios. The third type mentioned earlier involves rendering for each request, generating the initial page. However, if your page is the same for every user (e.g., the company introduction on an official website), there is no need to do this at runtime; it can be done at build time.

Therefore, one approach is to render the page during the build process, resulting in much faster speed.

This method is referred to as Static Site Generation (SSG) in Next.js.

How to Name the Different Types of SSR?

Let’s summarize the four types mentioned earlier:

  1. Rendering a different template for search engines and bots
  2. Pre-rendering for search engines
  3. Server rendering client app
  4. Render at build time

Different documents use different names for these types. Let’s take a look at a few examples.

web.dev

The first document is from web.dev: Rendering on the Web. At the end of the article, there is a spectrum:

SSR Spectrum

The first type is not specifically mentioned, the second type is more like “CSR with Prerendering,” but not exactly, the third type is “SSR with (Re)hydration,” and the fourth type is “Static SSR.”

According to this article, the definition of SSR is:

Server-side rendering (SSR): rendering a client-side or universal app to HTML on the server.

So, the first type, which does not render the client-side app on the server, should not be considered as SSR.

Next.js

The second document is from the official Next.js documentation: Building Your Application - Rendering.

Here, the third type is referred to as SSR, and the fourth type is called SSG. The definition here is slightly different again. It refers to the process of “generating SPA HTML on the server” as pre-rendering:

By default, Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and SEO.

And SSR specifically refers to “generating HTML for each request” to differentiate it from SSG.

Nuxt.js

Let’s start with Nuxt.js: link

In the documentation, the third type is referred to as “Universal Rendering,” which I think is a good term:

To not lose the benefits of the client-side rendering method, such as dynamic interfaces and page transitions, the Client (browser) loads the JavaScript code that runs on the Server in the background once the HTML document has been downloaded. The browser interprets it again (hence Universal rendering) and Vue.js takes control of the document and enables interactivity.

As for the definition of SSR, it doesn’t seem to be explicitly stated, but based on the following sentence:

This step is similar to traditional server-side rendering performed by PHP or Ruby applications.

It should be anything that involves “rendering on the server” can be called SSR.

Angular

Now let’s look at Angular: link

Their definition of SSR is:

Server-side rendering (SSR) is a process that involves rendering pages on the server, resulting in initial HTML content which contains initial page state.

This definition seems similar to the previous one, as long as it involves “rendering pages on the server,” it can be called SSR.

Summary of SSR

Now that I’ve written up to this point, I have some thoughts on SSR.

To be honest, I think I may have initially made the problem more complicated. SSR simply refers to “rendering on the server,” so as long as it meets this requirement, it can indeed be called SSR.

Originally, I only intended to write about the different SSR solutions mentioned earlier, but before I started writing, I became curious about the definition of SSR, which led to the introductory paragraphs exploring its history.

What’s more important is whether we can answer the questions of what problems SSR aims to solve, how to solve them, and the pros and cons of each solution. Not every webpage requires Next.js to achieve SSR; we should choose the appropriate technology based on the context.

Next, let’s talk about the present and the future.

Maximizing Performance and Building Faster Webpages

The third solution we mentioned earlier seems perfect, right? It allows us to render the page on the server, solving the performance issues related to SEO and first paint, while also enabling client-side hydration for a SPA-like experience.

However, there are still areas for continuous improvement.

We briefly mentioned a small issue with hydration earlier, where until hydration is complete, although the page is visible, it is not interactive. For example, typing in an input may not have any response because the event handler hasn’t been attached yet or the component hasn’t finished rendering.

So, what can we do about this? Another term comes into play: Progressive Hydration. Instead of hydrating the entire page at once, we can do it block by block, prioritizing the more important ones. This way, users can interact immediately after the important blocks are hydrated, and then the less important blocks can be hydrated.

Furthermore, you may notice that certain sections of a webpage don’t need hydration at all because they are static, such as a footer that remains the same throughout. In such cases, we can use another technique called Selective Hydration to pre-render the non-hydratable sections.

In 2019, Katie Sylor-Miller, the frontend architect at Etsy, proposed the Islands Architecture, which views a webpage as composed of different islands:

Islands Architecture

The above image illustrates the concept of selective hydration that was just discussed. When we adopt this architecture and combine it with selective hydration and other techniques, we can render faster and achieve better performance.

For example, Astro uses this architecture, where the entire page is static and only the interactive parts are separated into individual islands:

<MyReactComponent client:load />

React is also moving in this direction with server components, which is quite similar. By dividing the page into server and client components and determining which ones require state and which ones don’t, we can directly render the unnecessary components on the server and send them to the client, while maintaining the previous approach for the required components.

This approach does indeed accelerate web pages, but at the same time, development becomes more complex. There are more things to consider, and debugging becomes less convenient. I will share some insights and details in a future article.

Conclusion

I personally started exploring various frontend tools relatively late. Excluding the early days of using FrontPage or Dreamweaver, I began writing jQuery around 2012. Then, I observed the development of various frontend technologies but didn’t actually work with them. I had considered learning AngularJS (which was popular at the time) and Ember.js, but I was lazy.

It wasn’t until 2015 that I started working with React when it was just starting to gain popularity in Taiwan.

So, I didn’t participate in the era of Backbone.js and similar technologies. While writing this article, I researched a lot of information, which was quite interesting. It helped me fill in the gap of that period in history that I missed.

During my research, I also discovered that Yahoo! was truly a pioneer in frontend web development. For example, Atomic CSS originated from Yahoo!, and I also found out that Yahoo! was already using a Universal JavaScript web framework back in 2012.

If you have a different perspective on SSR or feel that I have misunderstood the historical context, feel free to write a new article to discuss it with me. After all, some concepts cannot be explained in a few words, and writing an article provides a more comprehensive explanation. Alternatively, we can also discuss it through comments.

References

  1. AJAX
  2. A Fond Farewell to YUI
  3. XMLHttpRequest
  4. Isomorphic JavaScript
  5. The Future (and the Past) of the Web is Server Side Rendering
  6. Rendering on the Web: Performance Implications of Application Architecture (Google I/O ’19)
A Bunch of Web and XSS Challenges Analysis of CVE-2023-46729: URL Rewrite Vulnerability in Sentry Next.js SDK

Comments