Why use server-side rendering?

1. Client rendering

2. Render on the server

3. Main factors of using SSR technology

  • First screen waiting:The TTFP (Time To First Page) of A CSR project takes a long Time
  • SEO : The SEO capability of CSR projects is extremely weak

4. React SSR process

SSR can be realized essentially because of the existence of virtual DOM

Second, the homogeneous

Concept: A set of React code executes once on the server and again on the client

// /containers/Home

const Home = () = > {
    return (
        <div>
            <div>This is allValue!</div>
            <button onClick={()= >{alert('click1')}}>
                click
            </button>
        </div>)}Copy the code

Click is not attached to the button on the server side, so we need to render the button on the client side again to bind the event

const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
    // Once found to be a core module, there is no need to merge the module's code into the final generated code
    target: 'node'.mode: 'development'.entry: './src/index.js'.output: {
        filename: 'bundle.js'.path: path.resolve(__dirname, 'build')},// Since these packages are already installed in the Node environment via NPM, they can be referenced directly without additional packaging into the code
    externals: [nodeExternals()],
    module: {
        rules: [{
            test: /.js? $/,
            loader: 'babel-loader'.exclude: /node_modules/,
            options: {
                presets: ['react'.'stage-0'['env', {
                    targets: {
                        browsers: ['last 2 versions']}}]]}}Copy the code
// src.index.js

import express from 'express';
import Home from './containers/Home';
import React from 'react';
import { renderToString } from 'react-dom/server';

const app = express();
app.use(express.static('public'));
const content = renderToString(<Home />);

app.get('/'.function (req, res) {
  res.send(`
        <html>
            <head>
                <title>ssr</title>
            </head>
            <body>
                ${content}
                <script src='/index.js'></script>
            </body>
        </html>
  `);
});

var server = app.listen(3000);
Copy the code

Third, introduce routing mechanism in SSR framework

  • To implement the React SSR architecture, we need to execute the same React code on both the client and server sides. The same React code here refers to the various component code we write, so in isomorphism, only component code can be common.

Why can’t routes be public?

In fact, the reason is very simple, on the server side, you need to find the routing component through the request path, and on the client side, you need to find the routing component through the web address in the browser, which is completely different from the two mechanisms, so this part of the code is definitely not public. Let’s take a look at the implementation code of front and back end routing in SSR:

Client routing:

The client-side routing code is very simple, and I’m sure you’re familiar with it. BrowserRouter will automatically display the corresponding routing component from the browser address.

const App = () = > {
    return (
        <Provider store={store}>
            <BrowserRouter>
                <div>
                    <Route path='/' component={Home}>
                </div>
            </BrowserRouter>
        </Provider>
    )
}

ReactDom.render(<App/>, document.querySelector('#root'))
Copy the code

With BrowserRouter we can match the routing component that the browser is about to display. For the browser, we need to convert the component to the DOM, so we need to use the reactdom.render method to mount the DOM.

Server side routing:

The server-side routing code is a bit more complex, requiring you to pass location (the current request path) to the StaticRouter component so that it can figure out which component is currently required based on the path.

PS: StaticRouter is a route component specifically provided by the React-Router for server-side rendering.

const App = () = > {
    return
        <Provider store={store}>
            <StaticRouter location={req.path} context={context}>
                <div>
                    <Route path='/' component={Home}>
                </div>
        </StaticRouter>
    </Provider>
}

Return ReactDom.renderToString(<App/>)
Copy the code

StaticRouter can match the component to be displayed on the server side. On the server side, we need to convert the component to a string. In this case, we need to call renderToString provided by ReactDom to get the HTML string for the App component.

To facilitate unified management, the actual route configuration is as follows

The details we can see it – > 👉 reactrouter.com/web/guides/…

routes: [

    {
        path: '/'.component: Home,
        exact: true.loadData: Home.loadData,
        key: 'home'
    },
    {
        path: '/goods'.component: Goods,
        exact: true.loadData: Goods.loadData,
        key: 'goods'
    },
    {
         ...xxxx

    {
        path: The '*'.component: NotFound,
        exact: true,},]Copy the code

4. Node middle layer

In the SSR architecture, Node is an intermediate layer that does server-side rendering of React code, and the data required by Node is usually provided by an API server alone.

This is done for engineering decoupling and to avoid some of the Node server’s computational performance issues. Why is it not suitable for intensive computing? Is this opinion correct and can it be solved?)

IO asynchronously completes the process by polling the queue to return data to the client, but this process requires the main thread to be executed. Due to intensive computations, the main thread will be blocked, resulting in timely response to asynchronous queue tasks.

The solution, child_process, etc., is to enable multiple processes or threads to handle CPU-intensive tasks, so the above approach is a very old idea

Process the data in the component

class Home extends Component {
    componentWillMount() {
        if (this.props.staticContext) {
            this.props.staticContext.css.push(styles._getCss()); }}render() {
        return(...). } } Home.loadData =(store) = > {
    return store.dispatch(getHomeList())
}
Copy the code
// Server processing of data
// matchedRoutes is a set of components corresponding to the current route that need to be displayed

matchedRoutes.forEach(item= > {
    if (item.route.loadData) {
        const promise = new Promise((resolve, reject) = >{ item.route.loadData(store).then(resolve).catch(resolve); }) promises.push(promise); }})Promise.all(promises).then(() = > {
    // TODO generates HTML logic
})
Copy the code

5. Handling of CSS

When we introduce some CSS styling code into our React code, the server-side packaging process will process the CSS once, and the client will process it again. Viewing the configuration, we can see that isomorphic-style-loader is used for server packaging. When it processes CSS, it only generates the class name on the corresponding DOM element, and then returns the generated CSS style code.

In the client code packaging configuration, we use CSS-loader and style-loader. Css-loader will not only generate the class name on the DOM, parse the CSS code, but also mount the code to the page through style-loader. However, since the style on the page is actually added when the client renders, there may be no style at the beginning of the page. To solve this problem, we can get the style code returned by isomorphic-style-loader when the server renders. It is then added as a string to the server-side rendered HTML

The client

// Client WebPack configuration
module: {
    rules: [{
        test: /\.css? $/,
        use: ['style-loader', {
            loader: 'css-loader'.options: {
                importLoaders: 1.modules: true.localIdentName: '[name]_[local]_[hash:base64:5]'}}}}]]Copy the code

The service side

When rendering on the server side, we can get the style code returned by isomorphic-style-loader and add it into the HTML rendered on the server side in the form of string

module: {
    rules: [{
        test: /\.css? $/,
        use: ['isomorphic-style-loader', {
            loader: 'css-loader'.options: {
                importLoaders: 1.modules: true.localIdentName: '[name]_[local]_[hash:base64:5]'}}}}]]Copy the code
const context = {css: []};
export const render = (store, routes, req, context) = > {
    const content = renderToString((
        <Provider store={store}>
            <StaticRouter location={req.path} context={context}>
                <div>
                    {renderRoutes(routes)}
                </div>
            </StaticRouter>
        </Provider>
    ));
    const cssStr = context.css.length ? context.css.join('\n') : ' ';
    
    return `
        <html>
            <head>
                <title>ssr</title>
                <style>${cssStr}</style>
            </head>
            <body>
                ...
            </body>
        </html>
    `;
}

class Home extends Component {
    componentWillMount() {
        if (this.props.staticContext) {
            this.props.staticContext.css.push(styles._getCss()); }}}Copy the code

Collect resources when the server goes out

When the server outputs HTML, you need to define CSS and JS resources for the client to download and use

// The way our project is handled
import { ChunkExtractor } from '@loadable/server';
import { ServerStyleSheet } from 'styled-components';

const extractor = newChunkExtractor({ statsFile }); .const { routerPath, search } = this.baseData || {};
const sheet = new ServerStyleSheet();

const jsx = extractor.collectChunks(
    sheet.collectStyles(
        <StaticRouter location={{ pathname: routerPath.search}} >
            <App
                i18nLang={this.i18nLang}
                pathname={routerPath}
                initialData={this.baseData}
                routeList={routeList}
            />
        </StaticRouter>,),);Copy the code

6. Data dehydration and water injection

Add water to the server:

Inject data into the window as window.context to create water injection

Dehydrate in client:

The client is used to fetch data

/ / water injection
// utils.js
<script>
    window.context = {
        store: ${JSON.stringify(store.getState())}
    }
</script>

/ / dehydration
export const getClientStore = () = >{
    const defaultState = window.context.store;
    return createStore(
        reducer, defaultState, applyMiddleware(thunk)
    );
}
Copy the code

7. Acquisition of asynchronous data + Redux in SSR

Client rendering

The use of asynchronous data with Redux follows the following flow (corresponding to step 12 in the figure) :

  1. Create the Store
  2. Displays components by route
  3. Send Action to get data
  4. Update data in the Store
  5. Component Rerender

The server side

Once the page has been Rerender, there is no way to Rerender, which requires the Store data to be ready when the component is displayed, so the process of using server-side asynchronous data in conjunction with Redux looks like this (corresponding to step 4 in the figure) :

  1. Create the Store
  2. Analyze the data required in the Store based on the route
  3. Send Action to get data
  4. Update data in the Store
  5. Combine data and components to generate HTML that is returned once

Below, let’s analyze the process of server-side rendering:

In client rendering, there is always only one Store in the user’s browser, so in code you can write:

// Client-side
const store = createStore(reducer, defaultState)export default store;

// Store becomes a singleton, shared by all users
// Returns a function that is reexecuted each time a user accesses it, providing a separate Store for each user
const getStore = (req) = > {
    return createStore(reducer, defaultState);
}

export default getStore;
Copy the code

Eight, the integration of SEO skills

1. The true functions of Title and Description

- The second-generation search engine is based on the full text of the website - Title and description have relatively little influence on the search - keywords that attract users appear in title to attract users to click and improve the conversion rate rather than the rankingCopy the code

2. How to do SEO well

  • Web site components: multimedia, links, text
  • When search engines judge the value of websites, they judge from these three aspects.
    • Text optimization — original
    • link
      • Internal links: links to content as relevant as possible to the original site.
      • External links: The more links there are, the more influence there is
    • Multimedia – can do picture recognition, original, hd

3. Use of react-Helmet

class Application extends React.Component {
    render () {
        return (
            <div className="application">
                <Helmet>
                    <meta charSet="utf-8" />
                    <title>My Title</title>
                    <link rel="canonical" href="http://mysite.com/example" />
                </Helmet>.</div>); }};/ / the server
const helmet = Helmet.renderStatic()
Copy the code

9. A new way to solve SEO problems with pre-rendering

Don’t want to use SSR but want to improve search engine ranking – pre-render

  • The middle tier visits the web page, takes the content and renders it into full HTML, and returns the full HTML to the client

For details, see prerender. IO /framework/

Using prerender, start a port number of 8000 to access the client render url localhost:8000/render? url=http://localhost:3000

Use the preRender server to distinguish spider access.

Nginx can be distinguished by userAgent

Ten,

The simple React project will become very complex using SSR technology, which will reduce the maintainability of the project and make it difficult to trace code problems.

Therefore, when using SSR to solve problems, it will also bring a lot of side effects, and sometimes these side effects are much more harmful than the advantages brought by SSR technology. It is generally not recommended to use SSR unless your project is heavily dependent on search engine traffic or has specific requirements for first screen time.

References:

  • [Issue 1443] Evolution of isomorphism (SSR) principle in React
  • React SSR server rendering and isomorphism
  • React server rendering principle analysis and practice
  • reactrouter.com
  • prerender.io/framework/
  • Reactrouter.com/web/guides/…