Knowledge carding and summary of building react server rendering project

Time:2021-4-11

GitHub address of the projectreact-koa2-ssr

The technology stack used react16. X + react-router4. X + koa2. X

preface

Some time ago, I did a simple ancient literature net in my spare time, but the project is rendered by react spa, which is not conducive to SEO, so I have the requirement of server-side rendering. Later, I want to write a demo to summarize the whole process and deepen my understanding of it. During the period, due to work, the process is intermittent. In a word, this project came later. About the advantages and disadvantages of server rendering,Rendering official documents on Vue serverIt’s the clearest. It’s the clearest. For most scenarios, the most important thing is to improve the first screen loading speed and facilitate SEO. In order to quickly build the development environment, we directly use create react app and koa2. X to generate a basic project. The whole project is developed on this basis. At present, it has only completed the most basic requirements. There are still many bugs and areas that can be optimized. Welcome to exchange.

Basic theoretical knowledge of server rendering

First, create react app and koa2 scaffold are used to generate the two projects, and then the two projects are combined. In this way, we save some tedious configuration of webpack, and the server uses Babel compilation. Before looking at this, I have mastered the knowledge of webpack and koa2. X, Babel by default.
Let’s go straight to the important steps. I think there are only three main points to build a react SSR environment
The first is the rendering API provided by react server, the second is the isomorphism of front-end and back-end routing, and the third is the isomorphism of initialization asynchronous data. Therefore, this simple demo mainly starts from these three aspects.

  • Conditions of react server rendering
  • Isomorphism between react-router4. X and koa2. X
  • Redux initial data isomorphism

Conditions of react server rendering

In fact, we can see Chapter 7 of “in depth react technology stack”, which is very detailed.
Generally speaking, the reason that react can achieve server-side rendering is that react DOM provides an API for server-side rendering

  • Rendertostring converts a react element to an HTML string with reactive.
  • Rendertostatic markup is converted to HTML string without reactid. If it is static text, this method will reduce a large number of reactid

The existence of these two methods can actually regard react as a template engine. Parsing JSX syntax into a normal HTML string.

We can call these two APIs to pass in the reactcomponent and return the corresponding HTML string to the client. After the browser receives this HTML, it will not render the DOM tree again, just do the event binding and other operations. This improves the performance of the first screen loading.

React-router4. X is isomorphic to the routing of server.

React-router 4. X is a big change compared with the previous version. The whole route has become componentized.
You can focus on the details given by the official hereExamples and documentationIt can be used as a reference for basic ideas and standards.

The difference between server-side rendering and client-side rendering is that the routing is stateless, so we need to wrap the app through a stateless router component, and match the specific routing array and its related attributes through the URL requested by the server.
So we use browserrouter on the client side and staticrouter on the server side.

  • Browserrouter uses the history API (pushstate, replacestate and popstate events) provided by HTML5 to keep the UI and URL synchronized.
  • Staticrouter is a router component that does not change the address.

The reference code is as follows:

//Server routing configuration
import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from './App'

createServer((req, res) => {
  const context = {}

  const html = ReactDOMServer.renderToString(
    <StaticRouter
      location={req.url}
      context={context}
    >
      <App/>
    </StaticRouter>
  )

  if (context.url) {
    res.writeHead(301, {
      Location: context.url
    })
    res.end()
  } else {
    res.write(`
      <!doctype html>
      <div id="app">${html}</div>
    `)
    res.end()
  }
}).listen(3000)
And then the client:import ReactDOM from 'react-dom'

//Client routing configuration
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), document.getElementById('app'))

We pass the routing URL of KOA into < staticrouter / >, and the latter will automatically match the corresponding react component according to the URL, so that we can refresh the page and the corresponding routing component returned by the server is consistent with that of the client.
At this point, we have been able to achieve page refresh, and the server and client are consistent.

Redux server isomorphism

First of all, the official document made a brief introductionhttp://cn.redux.js.org/docs/recipes/ServerRendering.html.

The processing steps are as follows:

  • We get the corresponding asynchronous method according to the corresponding server request API to get the asynchronous data.
  • 2 use asynchronous data to generate an initialized storeconst store = createStore(counterApp, preloadedState),
  • 3 then callconst finalState = store.getState()Method to get the initialization state of the store
  • 4 pass the initial initstate as a parameter to the client
  • When the client initializes, go back to judge the window__ INITIAL_ STATE__ Whether there is data below, and if so, regenerate a client’s store as the initial data

As shown in the following code.

Server

 <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__INITIAL_STATE__ = ${JSON.stringify(finalState)}
        </script>
        <script></script>
      </body>
    </html>    

client

...
//The initial state is obtained by injecting global variables into the server
const preloadedState = window.__INITIAL_STATE__

//Create Redux store with initial state
const store = createStore(counterApp, preloadedState)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

This is basically a standard Redux isomorphic process. In fact, more officials are providing us with a standardized idea. We can do more optimization along this.
First of all, we don’t need to use the API as a mapping tool directly. The server and the client do a set of asynchronous loading methods, which is very redundant.
The react router package providesreact-router-configIt is mainly used for static routing configuration.
The matchroutes API can return the corresponding route array according to the incoming URL. We can directly access the corresponding react component on the server side through this method. If you want to obtain asynchronous methods directly from routing, I have seen many similar isomorphic schemes,

  • There are mainly two ways. One is to add a chunk method to the route directly to obtain the initialized asynchronous data,

I think the advantage is that it is clear and intuitive, and this problem is solved directly at the routing layer.

  • The second is to use the static method of class. We can route to the static method under the component’s class. In this way, we can directly declare the server initialization method and the client initialization method inside the container component at the same time. I think the processing level is put into the component, which can better reflect the independence of the component.

This project adopts the second scheme. Let’s look at the code first

/**
 *Rendering server routing
 */
module.exports.render = async(ctx,next) =>{
    const { store ,history} = getCreateStore(ctx);
    const branch = matchRoutes(router, ctx.req.url);
    const promises = branch.map(({route}) => {
        const fetch = route.component.fetch;
        return fetch instanceof Function ? fetch(store) : Promise.resolve(null)
    });
    await Promise.all(promises).catch((err)=>{
        console.log(err);
    }); 

    const html = ReactDOMServer.renderToString(
                <Provider store={store}>
                            <StaticRouter
                            location={ctx.url}
                            context={{}}>
                                <App/>
                            </StaticRouter>
                </Provider>
        )
        let initState=store.getState();
        const body =  layout(html,initState);
   ctx.body =body;
}

The corresponding container component provides a static fetch method

class Home extends Component {
  ...
  static fetch(store){
        return store.dispatch(fetchBookList({page:1,size:20}))
  }

This is our actions

/**
 *Get book catalog
 * @param {*} param 
 */
export const fetchBookList = (params) => {
    return async (dispatch, getState) => {
        await axios.get(api.url.booklist, {
            params: params
        }).then((res) => {
            dispatch(booklist(res.data.result));
        }).catch((err) => {

        })
    }
}

First, we get all the routes under the current route through matchroutes, and then traverse them to get the promise array of an asynchronous method. Here, the so-called asynchronous method is the asynchronous method in actions. Because we also initialize the store on the server side, we can call actions directly on the server side. Here, we need to pass in the store to the static method of the container component, so that we can use thestore.dispatch(fetchBookList({page:1,size:20}))Actions is called. With the above method, we get a promise array. We use Promise.all All will be executed asynchronously. At this time, the store actually runs the same as the client. We write all the initial data to the store asynchronously. So we passedstore.getState()You can get the initialization data. The initialization of the client is the same as the official example of redux. Directly determine whether the initialization state is passed in. If it is passed in, it will be used as initialization data. How can we avoid the repetition of the asynchronous initialization of the server and the asynchronous initialization of the client. Here, we directly obtain the corresponding initial data in the store to see if it exists. If it does not exist, we will load it again.

At this point, we have been able to refresh the page asynchronous data server-side processing, do not refresh the page front-end processing, a basic isomorphic scheme body will come out, the rest is some optimization items and some project customization things.

Server page distribution

For the server, it will not only receive the front-end routing request, but also receive a variety of other static resource requestsimport {matchPath} from 'react-router-dom';Here, we use the matchpath API in the react router DOM package to match whether the current request routing is the same as our client’s routing configuration. If it is different, we default to request static resources or other resources. If it doesn’t match the current route, we directly execute next () to go to the next middleware. Because our project is actually a front-end and back-end separation project, only adding the way of server-side rendering. If the server has to process other requests, we can also add other routes through the server to match the corresponding rendering page and API through mapping.

other

I have read a lot of GitHub projects and related articles in this demo. These materials have great inspiration for this project

Vue.js Server side rendering Guide

react-server

beidou

react-ssr-optimization

React-universal-ssr

fairy

D2 – create a highly reliable and high-performance react isomorphic solution

GG + react server rendering development guide

Server rendering and app universal

Summary of isomorphic straight out optimization of react

Ultimate optimization of react mobile web

https://github.com/joeyguo

summary

We know how to render on the server
The advantage is that it can quickly optimize the first screen and support SEO. Compared with the Traditional spa, it has a more data processing method.
The disadvantages are also very obvious. Server rendering is equivalent to porting the processing flow of the client to the server, which increases the load of the server. Therefore, to make a good SSR scheme, caching is essential. At the same time, there are many areas worthy of optimization in engineering. Here is just a taste, and did not do the relevant processing, it is estimated that there will be time to do some optimization in the future, you are welcome to pay attention.

GitHub address of the projecthttps://github.com/yangfan0095/react-koa2-ssr

Above

Recommended Today

Deeply analyze the principle and practice of RSA key

1、 Preface After experiencing many dark moments in life, when you read this article, you will regret and even be angry: why didn’t you write this article earlier?! Your darkest moments include: 1. Your project needs to be connected with the bank, and the other party needs you to provide an encryption certificate. You have […]