About React Server-side Rendering (SSR)

Keywords: React Webpack Vue JSON

Recently, I often hear other front-end teams that specialize in Vue project mention SSR. They don't know what it is. They just think it's something they do inside. Although they are curious about it, they don't care much about it. Later, when they wander around a technology forum, they suddenly see the term. After clicking in, they know what SSR is. Things.

The concept of SSR

Server Slide Rendering, abbreviated as ssr, is server-side rendering. Because it comes from the back-end, it has long known what is going on, but it does not have the concept of this specific noun. This term is frequently mentioned thanks to the rapid development of the front-end in recent years, mainly for SPA applications, with the following purposes:

  1. Solving SEO in Single Page Application
    Most of the main HTML of single page application page is not returned by the server. The server only returns a large number of scripts. Most of the content seen on the page is generated by scripts, which has little impact on the general website. But for some websites which rely on search engine to bring traffic, it is fatal. Search engine can not grab the relevant content of the page, that is, users can not search it. The relevant information to this website, naturally there is no traffic to speak of.
  2. Solve the problem of rendering white screen
    Because page HTML is generated by scripts returned from server side, generally speaking, the volume of this script is not too small. Client download takes time and browser parse takes time to generate page elements. This will inevitably lead to page rendering slower than traditional server side. It is easy to appear the situation of white screen on home page. Even if the browser disables JS, then This will cause the page to be invisible even to the basic elements.

Vue and React are the most common frameworks for single-page applications, because I'm more familiar with react, so I'll just talk about React SSR here.

Client Section

New React page

First, you need to have a React page for presentation, such as:

// ./client/components/About/index.jsx
import React, { Component } from 'react'
import styles from './style.scss'
import './style'

export default class About extends Component {
    constructor() {
        super()
        this.state = {
            txtInfo: ''
        }
    }
    componentWillMount() {
        this.state.txtInfo = 'zhangsan'
    }
    componentDidMount() {
        this.getInfo()
    }
    render() {
        return (
            <section className={styles['about-wrapper']}>
                <p className="title">About Page</p>
                <p className="txt-info">{this.state.txtInfo}</p>
            </section>
        )
    }
    getInfo() {
        fetch('/api/user/getInfo', {
            credentials: 'include',
            headers: {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'text/plain; application/json; charset=utf8'
            }
        }).then(res=>{
            return res.json()
        }).then(data=> {
            this.setState({
                txtInfo: data.name
            })
        })
    }
}

The routing here uses react-router. For simplicity, there is only one route to create a routing:

// ./client/routes.js

export default const routes = {
    childRoutes: [{
        path: '/',
        component: require('./components/app.jsx'),
        indexRoute: {
            getComponent(nextState, callback) {
                require.ensure([], require => {
                    callback(null, require('./components/About/index.jsx'))
                }, 'about')
            }
        }
}

The structure of. / components/app.jsx is simple, and there is only one render method:

render() {
  const {children, ...props} = this.props
   return (
     <div>
       {React.Children.map(children, child =>
         React.cloneElement(child, {...props})
       )}
     </div>
   )
 }

Most importantly, the entry component index.js:

// ./client/index.js

import React from 'react'
import { render } from 'react-dom'
import { Router, match, browserHistory } from 'react-router'
import routes from './routes'

match({ history: browserHistory, routes }, (error, redirectLocation, renderProps) => {
    render(
        <Router {...renderProps}/>,
        document.getElementById('root')
    )
})

Among them, there is a match method, which is provided by react-router. It matches the routing component according to the URL before rendering. More details can be seen. Here

That's all for the client. Let's start the server.

Server side

The server uses the koa framework, installing and configuring what I don't say much, I don't know the visible Here

// ./server/app.js

import Koa from 'koa'
const app = new Koa()
export default app

Configure server-side routing:

// ./server/routes/index.js

import Router from 'koa-router'
const router = new Router({prefix: '/api/user'})

router.get('/getInfo', async(ctx, next)=> {
    ctx.body = {
        name: 'xiaoming',
    age: 18
  }
})
export default router

Configured a route to / api/user/getInfo

However, only server-side routing is not good, because it is server-side rendering, so client-side routing must also be configured on the server-side, because only knowing the client's route, the server knows what page HTML string to send to the client, as follows:

// ./server/clientRoutes.js

import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from '../../client/routes'

async function clientRoute(ctx, next) {
    let _renderProps

    match({ routes, location: ctx.url }, (error, redirectLocation, renderProps) => {
        _renderProps = renderProps
    })

    if (_renderProps) {
        await ctx.render('index', {
            root: renderToString(
                <RouterContext {..._renderProps}/>
            ),
            info: { name: 'Xiao Ming' }
        })
    } else {
        await next()
    }
}

export default clientRoute

In addition to match ing, this time there is another RouterContext, which is also a react-router API. Its function is to render the routing component in a synchronous way. Otherwise, you think, a react component, there are many API hook functions. If the server renders HTML to the client before these hook functions have been executed, it will surely be less than two kilograms, so that's why. Then the API comes in handy.

All that's left is to start the service rendering page.

This is a little more complicated, because we need to consider many problems, such as jsx grammar transcoding, css style file packaging, HTML injection, not only browser side, but also server side.

// Transcoder babel
require('babel-polyfill')
// react's transcoding hook
require('babel-register')({
    presets: ['es2015', 'react', 'stage-0'],
    plugins: ['add-module-exports']
})

// css transcoding hook
require('css-modules-require-hook')({
    extensions: ['.scss'],
    preprocessCss: (data, filename) =>
        require('node-sass').renderSync({
            data,
            file: filename
        }).css,
    camelCase: true,
    generateScopedName: '[name]__[local]__[hash:base64:8]'
})


const webpack = require('webpack'),
    app = require('./app'),
    convert = require('koa-convert'),
    fs = require('fs'),
    path = require('path'),
    devMiddleware = require('koa-webpack-dev-middleware'),
    views = require('koa-views'),
    router = require('./routes'),
    clientRoute = require('./clientRoute'),
    config = require('../webpack.dev.config'),
    port = process.env.port || 3000
    compiler = webpack(config)

app.use(views(path.resolve(__dirname, '../views/dev'), { map: { html: 'ejs' } }))
app.use(clientRoute)
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${port}`)
app.use(convert(devMiddleware(compiler, {
    noInfo: true,
    publicPath: config.output.publicPath
})))
app.listen(port)

Basically that's it. Opening the page is this effect:

At the same time, if you request a page with a tool like Restlet Client, you can see that the returned HTML string is exactly the same as the actual HTML of the page rendered by the browser, which indicates that the goal of server-side rendering has been achieved.

Related items

I didn't want to post some of the above code, because it's unnecessary.
If a year ago, there was no standard for react server rendering and all the gods of the community had their own myths, then it is no longer necessary to mention react server slide rendering. You should think of this scaffold: Next.js

Next.js is a simple framework for rendering React applications on the server side. On October 25, 2016, by zeit.co The team behind the release.

React server rendering SSR application framework, support optional server and client rendering function, easy to use, installation of this framework will build a process based on React, Webpack and Babel, that is to say, scaffolding has been pre-configured, developers do not need to build Webpack or Babel configuration.

This project is one of the most popular among many react server slide rendering schemes. If we consider react server rendering now, this scheme is the best choice.

In addition, not only react, but also vue has similar libraries for target: Nuxt.js Interestingly, Nuxt.js was released only a few hours after the announcement of Next.js. It was released on the same day, and now it has equal influence in their respective fields.

Posted by pbdude23 on Fri, 24 May 2019 11:19:29 -0700