Learn and Understand Server-Side Rendering (SSR)

What is SSR

Server-side rendering (SSR) is the ability of an application to render a web page by displaying it on the server instead of in the browser. The server sends a fully rendered page (just an HTML page to be exact) to the client. At the same time, combined with the client-side JavaScript bundle, the page can be run. As opposed to SSR, there is also a Client-side rendering (CSR). The biggest difference between CSR and SSR is that rendering is provided on the client-side or on the server-side. There is another kind of essence. Therefore, if the following does not focus on the differences between CSR and SSR, the default is the same.

Why Do We Use SSR

Thanks to the development of front-end frameworks such as react, the separation of front-end and back-end, the popularity of compilation tools such as webpack, and the partial refresh of pages by ajax, our current applications no longer need to obtain pages from the server as in the past. Local page data can be dynamically modified to avoid problems such as frequent page jumps affecting user experience. SPAs are increasingly becoming the mainstream application model.

The use of SPA, in addition to the advantages mentioned above, will inevitably bring disadvantages. For example,

1. Since all the JavaScript libraries required by the page need to be loaded before the page is loaded, it takes a long time to open the page for the first time.

2. Need to develop a web framework specifically for SPA (various frameworks with SSR capabilities, including Next.js, etc.).

3. Search engine crawlers

4. Problems with browser history (various routers based on pushState).

In order to solve problems 1. and 3. mentioned above, SSR began to enter the stage of history.

How to Do with SSR

Based on theory, we can design a React framework with SSR capabilities.

First, we initialize a React project through the create-react-app command, which can be understood as a project with the simplest functions. We will implement an SSR function based on this project.

# Yarn
$ yarn create react-app ssr-demo

It should be noted that when the current version of the cra command creates a new project, the startup will report a problem similar to Mini... is not a function. This is due to the update of the plugin version of the mini-css-extract-plugin. You only need to limit the version of mini-css-extract-plugin to 2.4.5 through resolutions in package.json.

The directory where the project is generated is as follows.

./
├── README.md
├── build
├── node_modules
├── package.json
├── public
├── src
└── yarn.lock

The dependencies have been automatically installed. After starting the project, we can see the simplest page in the "local environment".

Next, let's implement an SSR function. First, we need to install express (if it is a CSR, this step is not required).

yarn add express

After the installation is complete, we need to write the following code in the server/index.js file.

import express from "express";
import serverRenderer from "./serverRenderer.js";

const PORT = 3000;
const path = require("path");

const app = express();
const router = express.Router();

// When the crawler's request comes in, direct all requests to the serverRenderer route
router.use("*", serverRenderer);

app.use(router);
app.listen(PORT, () => console.log(`listening on: ${PORT}`));

The content of the serverRenderer file is as follows.

import React from "react";
import ReactdomServer from "react-dom/server";

import App from "../src/App";

const path = require("path");
const fs = require("fs");

export default (req, res, next) => {
  // Get the HTML template file path of the current project
  const filePath = path.resolve(__dirname, "..", "build", "index.html");

  // Read the file
  fs.readFile(filePath, "utf8", (err, htmlData) => {
    if (err) {
      console.error("err", err);
      return res.status(404).end();
    }

    // Render JSX into HTML string with the help of the method under react-dom dependency
    const html = ReactDOMServer.renderToString(<App />);

    // Replace HTML string into root
    return res.send(
      htmlData.replace('<div id="root"></div>', '<div id="root">${html}</div>')
    );
  });
};

As shown above, we have completed a very simple server with an SSR function. But this is not enough, we also need to create a new parser.js in the root directory to convert ESM to CommonJS and run it. The sample code is as follows.

require("ignore-styles");
require("@babel/register")({
  ignore: [/(node_modules)/],
  presets: ["@babel/preset-env", "@babel/preset-react"],
});

require("./server");

Here is the explanation of the role of the package introduced above.

@babel/register: This dependency will require files with extensions .es6, .es, .jsx, .mjs, and .js that are required by the subsequent runtime of node to be automatically converted by Babel.

ignore-styles: This dependency is also a Babel hook, which is mainly used to ignore the import of style files during Babel compilation.

After the above operations, we first yarn build our product, and then start the SSR service through node parser.js.

Now, we have designed a very simple but reasonable SSR server. For comparison, here we simply compare with Next.js. In the package.json in the root directory of the Next.js project, we can see that express is also selected as the server.

...
"eslint-plugin-react-hooks": "4.2.0",
"execa": "2.0.3",
"express": "4.17.0",
"faker": "5.5.3",
...

We can find in the ~/packages/next/server/next.ts folder that Next.js will start a NextServer object through the createServer method, which is responsible for starting the server and rendering the template.

On the official website of [Next.js](https://nextjs.org/docs/basic-features/pages#server-side-rendering), we can see that it supports the getServerSideProps function on the page to realize the dynamic acquisition interface data. In fact, in most framework libraries that support SSR, there are similar designs. Because of the application of SPA, it is inevitable to obtain dynamic data through the server and render the page, and the design ideas of SSR for rendering dynamic data are relatively consistent. That is, export a fixed method in the same file of the component of the page, and return a fixed format. The framework will use this data as initial data for the SSR rendering of the page.

How to Do with CSR

We take Next.js as an example to understand the general design idea of ​​SSR, then let's take a look at the general idea of ​​CSR. CSR can be understood as a shortened version of SSR, which only implements the pre-rendering function of SSR. It is generally used for static websites and does not have the function of dynamically obtaining data.

The rendering idea of ​​CSR is the same as that of SSR. The difference is that SSR needs to install express while CSR does not need to install express. This also leads to the difference in the deployment process of CSR and SSR. SSR projects such as Next.js applications can start the server through the start command after executing the build command, and no longer need to cooperate with the reverse proxy of Nginx. And CSR projects like Umi still need Nginx proxy.

The biggest difference between CSR is the difference in the compiled product. Usually, the compiled product of a front-end project includes the following aspects.

bundle.js or chunk.js

index.html

index.css

public/*

Other related files, such as rss.xml, etc.

After the project with CSR is compiled, there will be more HTML files, and the structure of these files will be generated according to the route. For example, our current routes are as follows, corresponding to ComponentA and ComponentB respectively.

/a

/b

Then a.html and b.html will be generated in our compiled product. After we deploy the product to the Nginx service, we can implement the pre-rendering function.

To achieve the above functions, the most important steps are as follows.

Get the route to the current item.

Obtain the component corresponding to the route. If the component has not been compiled, it needs to be compiled.

Render JSX into HTML with the help of react-dom and insert it into the template HTML.

Create a folder according to the route in the compiled product, and generate the resulting HTML into the corresponding path.

At this point, we have understood the entire SSR process, and I believe everyone has a certain degree of understanding of SSR. Most of the frameworks in the current community do not require us to do SSR by ourselves. Our understanding of the rendering process helps us stay the same when dealing with a wide variety of frameworks.



Leave a reply



Submit