A, what isSSR

1.CSRintroduce

Before introducing SSR, we can take a look at CSR. So what is CSR? In our traditional REACT/VUE project, we adopt a single-page (SPA) approach, i.e. only one HTML, with the structure roughly as follows:

< HTML >< head> <title> Traditional SPA application </title> </head> <body> <script SRC ="bundle.js"></script> </body> </ HTML >Copy the code

All page elements are rendered by bundled bundle.js.

client
server
ajax
json
CSR
js

2.SSRintroduce

SSR refers to Server Slide Render. That is, the back end returns an HTML page. Let’s write a simple server render. NPM init-> SRC /index.js in the react-ssr-frame folder:

var http=require("http");

const server=http.createServer(function(req,res){
    res.writeHead(200, {'Content-Type':"text/html"})
    res.end(` 
         REACT SSR   

Hello World

`
) }) server.listen(3000.'127.0.0.1'); Copy the code

Run the node SRC /index command and open localhost:3000

You can see that the back end returns an HTML page and renders directly to the page. To make sure that the content of our page is returned directly by the server. Chrome has an option to turn off the JS switch, so we turned it off. You can see that the page still displays normally (normally the page will fail to render under the CSR framework).

3.SSR VS CSR

CSR and SSR

3.1 CSR

  1. Browser downloadHTMLpage
  2. parsingHTMLThe resolution toscriptTags, downloadJSfile
  3. The browser acceptsJSFile, execute runReactcode
  4. The page starts rendering

3.2 SSR

1. The browser downloads the HTML page and renders the HTML page.

3.3 Comparison of advantages and disadvantages

From the above process, it is not difficult to find that SSR only needs to send one HTTP request, and CSR needs to send two HTTP requests (actually more than two) because it also needs to send HTTP request ICONS, etc. Because of the complexity of CSR steps, this creates a CSR disadvantage, which is the common problem of single-page apps that we often struggle with – slow first screen loading. Because the client has only one HTML page, when the search engine SEO crawler crawls, it only knows the content in HTML, not the content in JS, so it is not conducive to our ranking in the search engine. Thus, we concluded the advantages of SSR over CSR.

1. The first screen loading speed is fast.

2.SEO search engine ranking is better.

Although SSR has many advantages, it consumes server-side performance for SSR. We tried to use CSR when the first screen of the project was already fast and we didn’t need engine rankings.

3.4 the existingReactpopularSSRThe framework

The existing mainstream React SSR popular framework is next. Js. Next. How about implementing an SSR server rendering framework when we have time to look at the Next. Js documentation? Next, we will implement an SSR framework that can be used in actual projects step by step.

Second, server-side writingReactcode

We continue with the server project built in the previous chapter. React/Vue: NPM install NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React: NPM install React The React syntax is also unknown in the server node environment, which means we need to use Webpack for server rendering as well.

const path=require("path");
const nodeExternals=require("webpack-node-externals");
module.exports={
    // Since the browser and server side effects are different under the same require reference, the target platform needs to be specified
    target:"node".entry:"./src/index.js".mode:"development".output: {filename:"bundle.js".path:path.resolve(__dirname,'build')},externals:[nodeExternals()],
    Node_modules / / configuration after the require references will not be packaged, still can save before a reference form https://www.cnblogs.com/fanqshun/p/10073493.html
    module: {rules:[
            {
                test:/\.js? $/.loader:"babel-loader".exclude:/node_modules/}}}]Copy the code

The React-DOM provides a server-side rendering method, renderToString, that renders the React node as a string. The updated index.js page is as follows:

var http=require("http");
import React from 'react';
import Hello from './pages/Hello';
import { renderToString } from 'react-dom/server';

const content=renderToString(<Hello />)

const server=http.createServer(function(req,res){
    res.writeHead(200,{'Content-Type':"text/html; charset=utf-8"})
    res.end(`
        
       
        <html>
            <head>
                <title>REACT SSR</title>
            </head>
            <body>
                ${content}
            </body>
        </html>`)}) server. Listen (3000, '127.0.0.1);Copy the code

Under the Script tag in package.json

"build":"webpack --config webpack.server.config.js"."start":"node build/bundle.js"
Copy the code

Create a new. Babelrc configuration file in the project root directory

{
    "presets":[
        ["@babel/preset-env", {
            "modules": false."targets": {
              "browsers": ["1%" >."last 2 versions"."ie >= 10"]},"useBuiltIns": "usage"}]."@babel/preset-react"]}Copy the code

Now let’s review the project catalog

yarn add react-dom webpack webpack-cli webpack-node-externals babel-loader @babel/core @babel/node @babel/preset-react @babel/preset-env --save-dev 
Copy the code
  • Open a window to executenpm run buildPackaging project
  • Open a window to executenpm run startRun the project

After the package, we will see that a build directory is generated in the root directory, and bundle.js is the source directory generated after the package. Then execute the run command to start the packaged bundle.js. Then we open the browser and see:

The node_modules directory should be deleted and installed again. However, the deletion of node_modules can cause all kinds of exceptions. Here we use Rimraf to delete our node_modules package

  1. The installationrimraf :npm install rimraf --save-dev
  2. package.jsonIn thescriptDefine the DELETE field"delete": "rimraf node_modules"
  3. Execute the commandnpm run delete

Writing React on the server is basically a success. But have you found such a problem? After we modify the code each time. We need to pack it up and run it first, but that’s definitely not going to work normally. We use –watch to listen for file changes and Nodemon to listen for package file changes and execute node to run the project. First change the build command

"build": "webpack --config webpack.server.config.js --watch"
Copy the code

— Watch can listen for file changes and generate package NPM install nodemon –save

"start": "nodemon --watch build --exec node build/bundle.js"
Copy the code

npm-all-run

npm install npm-run-all --save
"scripts": {
    "delete": "rimraf node_modules".//--parallel indicates the file behind the parallel execution
    "dev": "npm-run-all --parallel dev:**"."dev:build": "webpack --config webpack.server.config.js --watch"."dev:start": "nodemon --watch build --exec node build/bundle.js"
},
Copy the code

Run NPM run dev, as shown below, and you’re done

3. Event response

In the last chapter, you wrote the code, and I guess a lot of you thought that was all you had to do with server-side rendering, right? But that’s not the case. We add a click event to the page.

<button onClick={()=>alert("2")}> </button>Copy the code

network

enderToString
renderToString

1. What is isomorphism

In short, a set of code runs once on the server side and again on the client side. Such applications are isomorphic.

2. Create isomorphic apps

We already have the server-side code, so let’s write the client-side code. We move the index.js file from SRC to the newly created server folder where the server code is written. Create a new client folder named Client, which also places an index.js file to write the client rendering code.

// This is how our typical client renders the mount node, except we need to use hydate instead of render
import React from 'react';
import ReactDom from 'react-dom';
import Hello from './pages/Hello';

ReactDom.hydrate(<Hello />,document.getElementById("root"))
Copy the code

Then we need to create a node with id root for easy mount.

<! doctype html><html>
    <head>
        <title>REACT SSR</title>
    </head>
    <body>
        <div id="root">
            ${content}
        </div>
        <script src="./public/index.js"></script>
    </body>
</html>
Copy the code

The script tag inside the body contains the code for client rendering. Then we still need to package the client code. Create the client package file webpack.client.config.js.

const Path=require("path")

module.exports={
    entry:"./src/client/index.js".mode:"development".output: {filename:"index.js".path:Path.resolve(__dirname,'public')},module: {rules: [{test:/\.js? $/.loader:"babel-loader".exclude:/node_modules/}}}]Copy the code

The script tag declares a command

"dev:build:client":"webpack --config webpack.client.config.js --watch".Copy the code

Run the NPM run dev command to see that the public directory and the index.js file are generated under the public directory.

We need to change the Node server file

const server = http.createServer(function (req, res) {

    let path = req.url;
    if (path === "/") {
        res.writeHead(200, { 'Content-Type': "text/html; charset=utf-8" });

        res.end(` <! DOCTYPE html> <html> <head> <title>REACT SSR</title> </head> <body> <div id="root">${content}
                </div>
                <script src="/index.js"></script>
            </body>
        </html>
    `)}else if(path==="/index.js") {let filePath=Path.join("public",req.url);
        fs.readFile(filePath,function(err,data){
            if(err){
                throw err;
            }
            // Set the request header to access a js file
            res.setHeader("Content-Type"."text/js"); res.end(data); })}})Copy the code

If the access path is index.js, return the index.js file in the public directory. Then we click the button to pop up the box.

3. Isomorphism principle analysis

First, let’s analyze:

  1. When the user enters localhost:3000, the browser request path is /.

  2. The Node server returns an HTML.

  3. The browser receives HTML to render HTML.

  4. The browser loads the JS file.

  5. The React code in JS is executed in the browser

  6. The React code in JS takes over the actions of the page

When the page is rendered, the redirect is controlled by JS. JS files in React take over the page. Not every page is rendered server side. The first page visited is rendered server side.

Therefore, we probably know that THE main function of SSR is to shorten the white screen event and SEO. SSR is inseparable from CSR.

Iv. Routing Processing (Level 1)

With event handling out of the way, the main thing is routing. Everyone knows that there must be multiple pages in a project, each with its own routing configuration. Route configuration has always been a difficulty in React. Rendering the routing configuration on the server side is different from rendering the configuration on the client side.

  1. When we use client rendering, the page jump route is controlled by the front end, and there are mainly two modes, one is history mode and the other is hash mode.
  2. Route jumps can be performed in either mode, the only difference being that history mode requires backend control of the 404 page, while hash mode does not.
  3. Routing control, not only on the server, the client also needs to carry out the same routing rules, then we can write a route for the client and server to use together

1. Set up routes

First, install the react-router-dom, create a router folder and create an index.js file under it to store the common page routing files rendered by the server and the client.

import Hello from '. /.. /pages/Hello';
import { Route } from 'react-router-dom';
import React from 'react';

export default(
    <div>
        <Route path="/" exact component={Hello}/>
    </div>
)
Copy the code

Client render file

import React from 'react';
import ReactDom from 'react-dom';
import Hello from '. /.. /pages/Hello';
import Routes from '. /.. /router/index';
import { BrowserRouter } from 'react-router-dom';

const App=(a)= >{
    return(
        <BrowserRouter>
            {Routes}
        </BrowserRouter>
    )
}

ReactDom.hydrate(<App />,document.getElementById("root"))
Copy the code

Server-side rendering is different from client-side rendering. BrowserRouter can intelligently sense changes in your page path and match the corresponding routing file, but server-side rendering can’t directly sense changes in your route, so we write the server-side route in the request method and pass the path to the server-side rendering component.

Because the server does not know the mode of the request, and the server does not need to know, the server only needs to do the corresponding processing according to the request sent by the client.

The StaticRouter needs to pass two properties, one context and one location.

Context is used to pass data information to components. This property can pass parameters, such as CSS-style parameters, between the client and server.

Location is used to receive request path information, such as pathName, search, hash, etc.

var http = require("http");
var fs =require("fs");
var Path=require("path");
import Routes from '. /.. /router/index';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';

const server = http.createServer(function (req, res) {
    const content = renderToString((
        <StaticRouter location={req.path} context={{}}>
            {Routes}
        </StaticRouter>
    ))

    let path = req.url;
    if(path.split(".").length===1) {// Check if it is a resource file
        res.writeHead(200, { 'Content-Type': "text/html; charset=utf-8" });
    
        res.end(` <! DOCTYPE html> <html> <head> <title>REACT SSR</title> </head> <body> <div id="root">${content}</div>
                    <script src="/index.js"></script>
                </body>
            </html>
        `)}else if(path==="/index.js") {let filePath=Path.join("public",req.url);
        fs.readFile(filePath,function(err,data){
            if(err){
                throw err;
            }
            // Set the request header to access a js file
            res.setHeader("Content-Type"."text/js");
            res.end(data);
        })
    } 

})

server.listen(3000.'127.0.0.1');
Copy the code

From the routing file above we get:

The StaticRouter component is added, passing in a context value and passing the req.url to the Location property so that the server can know the routing address of the client

  1. The client changes the address of the request through routing, and the server receives it. In StaticRouter we get the routing address of the request from the client.
  2. Routes start matching. If a match is found, the matched route component is rendered as an HTML string and sent to the client.
  3. If there is no match to, if we visited http://localhost:3000/hello routing, so is not matching our routes, since can’t match, then renderToString rendering result is empty, DomContent is inserted into the HTML template as a null value, resulting in a blank page

Let’s create a new linkdemo.js page to see if it works

import React from 'react';
import { Link } from 'react-router-dom';

export default class LinkDemo extends React.Component{
    render(){
        return (
            <div>
                LINK
            </div>)}}Copy the code

2. UseLinkThe tag jumps between routes

import React from 'react';
import { Link } from 'react-router-dom';

export default class LinkDemo extends React.Component{
    render(){
        return (
            <div>
                LINK
                <Link to="hello">hello</Link>
            </div>)}}Copy the code

Clicking Hello takes you to the Hello page.

3. Modify routes

We need to modify the routing

// Root routing file
import Hello from '. /.. /pages/Hello';
import Link from '. /.. /pages/Link';

export default[{path:"/".exact:true.component:Hello,
        key:"/"
    },
    {
        path:"/link".exact:true.component:Link,
        key:"/link"}]// Client render
<BrowserRouter>
            {
                Routes.map(props= >(
                        <Route {. props} / >))}</BrowserRouter>
// Server render
<StaticRouter location={req.path} context={{}}>
            {
                Routes.map(props= >(
                        <Route {. props} / >))}</StaticRouter>
Copy the code

Fifth, style related processing

1. Icon processing

Throughout large and small websites, ICONS are a very common part, so how to achieve such a function? In fact, the browser when we enter the address, will silently send a request to get the site icon, used to get the site icon.

x-icon
.ico
Online ICO generation
public
public
node

if(path.split(".").length===1){
        res.writeHead(200, { 'Content-Type': "text/html; charset=utf-8" });
    
        res.end(` <! DOCTYPE html> <html> <head> <title>REACT SSR</title> </head> <body> <div id="root">${content}</div>
                    <script src="/index.js"></script>
                </body>
            </html>
        `)}else if(path.split(".").length===2&&path.split(".") [1= = ="js") {// Determine the request js file
        let filePath=Path.join("public",req.url);
        fs.readFile(filePath,function(err,data){
            if(err){
                throw err;
            }
            // Set the request header to access a js file
            res.setHeader("Content-Type"."text/js"); res.end(data); })}else if(path.split(".").length===2&&path.split(".") [1= = ="ico") {// Determine the request ico file
        let filePath=Path.join("public",req.url);
        fs.readFile(filePath,function(err,data){
            if(err){
                throw err;
            }
            // Set the request header to access the file type icon file
            res.setHeader("Content-Type"."image/x-icon"); res.end(data); })}Copy the code

Restart NPM run Dev and you’ll find it in the upper left corner of the TAB TAB

2. Loader description

Css-loader: converts CSS files into JS files and imports them. In order to avoid confusion of classes, you can use CSS Modules to modularize the CSS. Modularizing transforms class names according to certain configured rules

// No CSS module
module.exports = "Styles in the original CSS file";
/ / add CSS module
module.exports = {
	toString: (a)= >"Styles in the original CSS file (class names converted, code probably compressed)" locals: {[class names written in the original file, etc.]: "actual class names converted, etc."}}Copy the code

Style-loader: In normal cases, after the CSS is processed by the CSS-loader, the CSS object is assumed to be cssContent, and then the style of the assumed CSS object is cssContent is simply inserted into the style tag through DOM manipulation.

varCssContent = [result of csS-loader converting CSS files]; addStylesToDom(cssContent.toString());module.exports = cssContent.locals;
Copy the code

Isomorphic-style-loade: Because there is no DOM in node environment, isomorphic-style-loade cannot be simply and harshly mounted to DOM like style-loader. Different from style-loader, isomorphic-style-loader exports some auxiliary methods. Let the user call different methods depending on the situation.

varContent = [result of csS-loader converting CSS files];// Make it easy for users to use class names, etc
exports = module.exports = content.locals || {}; 
exports._getContent = (a)= > content;
// Make it easy for users to get styles
exports._getCss = (a)= > context.toString();	
// make it easy for users to insert styles into the DOMExports._insertcss = "addStylesToDom";Copy the code

3.cssStyle to deal with

This part is mainly related to Webpack. You can read my other article webpack4 to build a development environment. In the last section, we saw that you need to turn csS-Module on to ensure that CSS is globally unique and that style clutter is avoided.

We create a new CSS file and introduce CSS styles in the hello.js file

import React from 'react';
import { Link } from 'react-router-dom';
import styles from "./index.css";

export default class Hello extends React.Component{
    render(){
        return (
            <div className={styles.hello}>Hello world changes<button onClick={()= >Alert ("2")}> Click me!!</button>
                <Link to="/link">Link</Link>
            </div>)}}/ / index. CSS styles
.hello{
    background-color: aqua;
}
Copy the code

1. First we need to install processingcssNeed to pack

npm install style-loader css-loader --save
Copy the code

2. We write under the server/client package fileloaderThe processing of

//webpack.client.config.js
{
        test:/\.css? $/.use: ['style-loader',
            {loader:'css-loader'.options: {modules:true}}].exclude:/node_modules/
}
//webpack.server.config.js
{
        test:/\.css? $/.use: ['style-loader',
            {loader:'css-loader'.options: {modules:true}}].exclude:/node_modules/
}
Copy the code

Reruning the code, we see that the terminal reported such an error

document is not defined
style-loader
css
document
document
style-loader

npm install isomorphic-style-loader --save
Copy the code

Then we changed the style-loader to isomorphic-style-loader in the server-side package file. This library can render CSS on the server side.

{
    test:/\.css? $/.use: ['isomorphic-style-loader',
        {loader:'css-loader'.options: {modules:true}}].exclude:/node_modules/
}
Copy the code

Restart the project, style has succeeded!

4. Problems loom large

When we disable javascript on the page, we will find that the style disappears. When we open the source code of the page, we can see that the class has been loaded on the DOM node. And when we refresh repeatedly, the page will shake. This is because the head tag does not have a style when the server first renders it, and the style is only written into the head when js is loaded. This is a process from scratch, so there will be a certain amount of shaking.

isomorphic-style-loader
head
isomorphic-style-loader
_getContent, _getCss, _insertCss
styles
css
css-loader
getCss

constructor(props){
        super(props);
        console.log("contrucotr");
        if(styles._getContent){
            console.log(styles._getCss()); }}Copy the code

So we can get the current CSS from _getCss, so how do we inject our CSS into the head header? When we used StaticRouter in routing, we said that the context property allows the route to pass through the current page.

// Declare the object and inject it into StaticRouter
let context={
        css:[]
};

const content = renderToString((
        <StaticRouter location={req.path} context={context}>
            {
                Routes.map(props=>(
                        <Route {. props} / >))}</StaticRouter>
))
Copy the code

The routing page gets a staticContext attribute at this point. Let’s print it out.

let staticContext=this.props.staticContext;
if(styles._getContent){
            console.log(styles._getCss());
            console.log(staticContext);
}
Copy the code

css
css

constructor(props){
        super(props);
        let staticContext=this.props.staticContext;
       
        if(styles._getContent){ staticContext.css.push(styles._getCss()); }}Copy the code

Next, print the context under the route.

css
The head of the style

 let cssStr = context.css.length ? context.css.join('\n') : ' ';
 
       
            <html>
                <head>
                    <title>REACT SSR</title>
                    <style>${cssStr}</style>
                </head>
                <body>
                    <div id="root">${content}</div>
                    <script src="/index.js"></script>
                </body>
</html>
Copy the code

Next we disable JS style can still be displayed, and no page jitter phenomenon.

5. Less configuration

Less can make our CSS code more concise, and the reuse will be greatly improved. Next we’ll show how to configure less:

1. Install it accordinglyloader

npm install less less-loader --save
Copy the code

2. Server configuration filerulesadd

{
                test:/\.less? $/.use: ['isomorphic-style-loader',
                    {loader:'css-loader'.options: {modules:true
                    }},
                    "less-loader"].exclude:/node_modules/
}
Copy the code

3. Client configuration filerulesadd

{
                test:/\.less? $/.use: ['style-loader',
                    {loader:'css-loader'.options: {modules:true
                    }},
                    "less-loader"].exclude:/node_modules/
}
Copy the code

Then create index.less file test found normal, can run.

scssconfiguration

Like less, this is one of our common configurations.

1. Install it accordinglyloader

npm install scss scss-loader sass-loader node-sass--save
Copy the code

2. Add it to the server configuration file rules

{
                test:/\.scss? $/.use: ['isomorphic-style-loader',
                    {loader:'css-loader'.options: {modules:true
                    }},
                    "scss-loader"].exclude:/node_modules/
}
Copy the code

3. Add it under client configuration file rules

{
                test:/\.scss? $/.use: ['style-loader',
                    {loader:'css-loader'.options: {modules:true
                    }},
                    "scss-loader"].exclude:/node_modules/
}
Copy the code

SCSS file test found normal, can run.

6. Optimize with higher-order functionscsscode

If we did this in the constructor of each file, the code would inevitably be redundant. We can use higher-order functions to optimize our code for reuse. For higher-order functions, you can see another article in my nuggets higher-order functions. Create a Component folder to hold our higher-order functions and create a connectstyle.js file. Processing logic is obviously much more reusable in higher-order functions.

//ConnectStyle.js
import React, { Component } from 'react';

export default (WrappedComponent, styles) => {
  class NewComponent extends Component {
    componentWillMount() {
      if (this.props.staticContext) {
        this.props.staticContext.csses.push(styles._getCss());
      }
    }

    render() {
      return (<WrappedComponent {. this.props} / >); }}; return<NewComponent />;
};
Copy the code

Here we will use the decorator syntax

// Install the Babel plugin to convert the decorator syntax
npm install @babel/plugin-proposal-decorators --save
/ /. Babelrc configuration
"plugins":[
        ["@babel/plugin-proposal-decorators", { "legacy": true}]]// Higher order function WithStyle
import React, { Component } from 'react';

export default (styles) =>(WrappedComponent) = > {
  class NewComponent extends Component {
    constructor(props) {
      super(props);
      let staticContext=this.props.staticContext;
       
      if(styles._getContent){
          staticContext.css.push(styles._getCss());
      }
    }

    render() {
      return (<WrappedComponent {. this.props} / >); }}; return NewComponent; }; Import React from 'React '; import { Link } from 'react-router-dom'; import styles from './Test.scss'; import ConnectStyle from './.. /component/ConnectStyle'; @ConnectStyle(styles) class Hello extends React.Component{ render(){ return (<div className={styles.color}>Hello world changes<button  onClick={()= >Alert ("2")}> Click me!!</button>
                <Link to="/link">Link</Link>
            </div>
        )
    }
}

export default Hello;
//Test.scss
.color {
    color:red;
}
Copy the code

Vi.404Page processing and page redirection

In Chapter 4 of this article, if the user does not define a route, the server route connects to the requested route by default after the user enters an address. If there is no match, renderToString will render empty, and domContent will be inserted into the HTML template as a null value, resulting in a blank page. What does that mean when the user sees nothing? At first glance, I thought there was a code error. This is where 404 becomes very important.

1.404 pages

The 404 page requires the client to tell the server if the page matches and what to do if it doesn’t, so we still need to use the Context property StaticRouter to pass the data. Create a 404 component, / SRC /pages/NotFound

import Hello from '. /.. /pages/Hello';
import Link from '. /.. /pages/Link';
import NotFound from '. /.. /pages/NotFound';

export default[{path:"/hello".exact:true.component:Hello,
        key:"hello"
    },
    {
        path:"/link".exact:true.component:Link,
        key:"link"
    },
    {
        path:"*".component:NotFound   
    }
]
Copy the code

If the path entered by the user is NotFound, the NotFound page is displayed. You need to mount a NotFound attribute to the routed context on the NotFound page.

import React from 'react';
import { Link } from 'react-router-dom';
import styles from './Test.scss';
import ConnectStyle from '. /.. /component/ConnectStyle';

@ConnectStyle(styles)
class NotFound extends React.Component{

    componentWillMount(){
        const { staticContext } = this.props;
        staticContext && (staticContext.NotFound = true);
    }

    render(){
        return (
            <div className={styles.color}>
               NotFound
            </div>)}}export default NotFound;
Copy the code

Determine if the Context has a NotFound attribute, pass the NotFound page to the client and change its status to 404

if (context.NotFound) {
            res.writeHead(404, { 'Content-Type': "text/html; charset=utf-8" });
            res.end(` <! DOCTYPE html> <html> <head> <title>REACT SSR</title> <style>${cssStr}</style>
                    </head>
                    <body>
                        <div id="root">${content}</div>
                        <script src="/index.js"></script>
                    </body>
                </html>
            `)
            return ;
}
Copy the code

Note that client and server routes are wrapped with Switch tags when rendering, otherwise multiple matches will be made.

Enter AAA test succeeded.

2. Redirect the page

Let’s say we have a page that we need to log in to see, but the user hasn’t logged in, so he can’t see it, but we want to redirect the page to the login page, so the user can log in, so we need to redirect.

We assume that we jump to the login page when we log in to the Link page.

1. Define a login page first

import React from 'react';
import { Link } from 'react-router-dom';
import styles from './Test.scss';
import ConnectStyle from '. /.. /component/ConnectStyle';

@ConnectStyle(styles)
class Hello extends React.Component{
    render(){
        return (
            <div className={styles.color}>Login screen</div>)}}export default Hello;
Copy the code

2. Change the route configuration

import Hello from '. /.. /pages/Hello';
import Link from '. /.. /pages/Link';
import NotFound from '. /.. /pages/NotFound';
import Login from '. /.. /pages/Login';

export default[{path:"/hello".exact:true.component:Hello,
        key:"hello"
    },
    {
        path:"/link".exact:true.component:Link,
        key:"link"
    },
    {
        path:"/login".exact:true.component:Login,
        key:"login"
    },
    {
        path:"*".component:NotFound,
        key:"notfound"}]Copy the code

3. Change thelinkpage

import React from 'react';
import { Link,Redirect } from 'react-router-dom';

export default class LinkDemo extends React.Component{
    render(){
        return (
            <div>
                LINK
                <Link to="hello">hello</Link>
                <Redirect to="/login" />
            </div>)}}Copy the code

This is typical client side route rendering, this time route can complete jump, but you turn js off, we also need to configure the server side render jump. So let’s print the value of the context.

There is an action property with the value REPLACE. This is done by react-router-dom. When a page is redirected using the Redirect component, action, location, and URL properties are added to the context. We can use this property to determine and redirect the login page

if (context.action==="REPLACE") {/ / 404
            console.log(context.url)
            res.writeHead(302, {'Location': context.url
            })
            res.end();
            return ;
}
Copy the code

At this point, the code for this chapter is complete. Server routing complete code

var http = require("http");
var fs =require("fs");
var Path=require("path");
import Routes from '. /.. /router/index';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter,Route,Switch } from 'react-router-dom';

const server = http.createServer(function (req, res) {

    let context={
        css:[]
    };
 
   
    const content = renderToString((
        <StaticRouter location={req.url} context={context}>
            <Switch>
                {
                    Routes.map(props=>(
                            <Route {. props} / >))}</Switch>
        </StaticRouter>
    ));

    let cssStr = context.css.length ? context.css.join('\n') : '';

    let path = req.url;

    if(path.split(".").length===1){

        if (context.NotFound) {//404
            res.writeHead(404, { 'Content-Type': "text/html; charset=utf-8" });
            res.end(`
                
       
                <html>
                    <head>
                        <title>REACT SSR</title>
                        <style>${cssStr}</style>
                    </head>
                    <body>
                        <div id="root">${content}</div>
                        <script src="/index.js"></script>
                    </body>
                </html>
            `)
            return ;
        }

        if (context.action==="REPLACE") {//404
            console.log(context.url)
            res.writeHead(302,{
                'Location': context.url
            })
            res.end();
            return ;
        }
         
        res.writeHead(200, { 'Content-Type': "text/html; charset=utf-8" });
    
        res.end(`
            
       
            <html>
                <head>
                    <title>REACT SSR</title>
                    <style>${cssStr}</style>
                </head>
                <body>
                    <div id="root">${content}</div>
                    <script src="/index.js"></script>
                </body>
            </html>`) }else if(path.split(".").length===2&&path.split(".")[1]==="js"){ let filePath=Path.join("public",req.url); fs.readFile(filePath,function(err,data){ if(err){ throw err; } // Set the request header to js file res.setheader (" content-type ", "text/js"); res.end(data); }) }else if(path.split(".").length===2&&path.split(".")[1]==="ico"){ let filePath=Path.join("public",req.url); fs.readFile(filePath,function(err,data){ if(err){ throw err; } // set the request header to the icon file res.setheader (" content-type ", "image/x-icon"); res.end(data); })}}) server.listen(3000, '127.0.0.1',function(){console.log("node is listening...") )});Copy the code

Life cycle function

In developing the React project, lifecycle functions made development easier and easier. So can we use life cycle functions in our server-side rendering? We could give it a try.

The Construtor constructor can be used in a server rendering

UNSAFE_componentWillMount is the only lifecycle function called by the server rendering.

componentDidMount(){
    console.log("componentDidMount")}Copy the code

We’ll write in our componentDidMount function on the Hello.js page. So let’s run it, and in the client render we can see the printed componentDidMount. So we didn’t print componentDidMount in our terminal. React: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {react: componentDidMount () {

So what do we do to access componentDidMount for a route?

Let’s go to the react-router-dom website

{
        path:"/hello".exact:true.component:Hello,
        key:"hello".loadData:Hello.loadData
},
Copy the code

Hello.loadData corresponds to the loadData method in the Hello component.

import React from 'react';
import { Link } from 'react-router-dom';
import styles from './Test.scss';
import ConnectStyle from '. /.. /component/ConnectStyle';

@ConnectStyle(styles)
class Hello extends React.Component{

    render(){
        return (
            <div className={styles.color}>Hello world changes<button  onClick={()= >Alert ("2")}> Click me!!</button>
                <Link to="/link">Link</Link>
            </div>
        )
    }
}

Hello.loadData=(a)= >{
    console.log("loadData")}export default Hello;

Copy the code

We can do this in the server render

const matchRoutes=[];// Define a matching route

Routes.some(route= >{
        // Traverses to match the corresponding route
        const match=matchPath(req.url,route);
        // If a match is found, the matched route is pushed to matchRoutes
        if(match){
            matchRoutes.push(route)
        }
})
Copy the code

There should be a routing component in matchRoutes. We can then make the loadData in the component execute once.

matchRoutes.forEach(item= >{
        item&&item.loadData&&item.loadData()
})
Copy the code

Now we can see the loadData printed on the terminal, and we’re done!

Eight, to optimizeSSR

1. The tip

When we search for keywords, the top ones with ads are paid ads that rank high.

2.SEO

If you do not spend money, but also want to top their website rankings, you can through IT related technology search engine optimization to make their website rankings, then the search engine technology is called SEO.

3.TitleandDescriptionThe true role of

For example, if you search for keywords, the search engine will not only display them by Title, but also rank them in full text search.

4. How to do it wellSEO

How to do SEO to make the site ranking? The website consists of three parts: text, multimedia and links

  • Optimizing text search engines requires understanding your value, and originality is more valuable than plagiarism, so originality is more valuable.
  • Optimized links The more external links, the stronger the relevance, the higher the value.
  • Optimize multimedia/pictures picture hd variety is more valuable.

5.React-HelmetThe use of

React-helmet can add title descriptions and more to our web pages.

import React from 'react';
import { Link } from 'react-router-dom';
import styles from './Test.scss';
import ConnectStyle from '. /.. /component/ConnectStyle';
import { Helmet } from 'react-helmet';

@ConnectStyle(styles)
class Hello extends React.Component{

    render(){
        return (
            <div className={styles.color}>
                 <Helmet>
                     <title>ReactHelmet use</title>
                    <meta name="Description" content="It's hello!"></meta>
                 </Helmet>Hello world changes<button  onClick={()= >Alert ("2")}> Click me!!</button>
                <Link to="/link">Link</Link>
            </div>
        )
    }
}

Hello.loadData=(a)= >{
    console.log("loadData")}export default Hello;

Copy the code

// Server render
import { Helmet } from 'react-helmet';
const helmet=Helmet.renderStatic();

       
<html>
        <head>
            ${helmet.title.toString()}
            ${helmet.meta.toString()}
            <style>${cssStr}</style>
        </head>
        <body>
            <div id="root">${content}</div>
            <script src="/index.js"></script>
        </body>
</html>
Copy the code

6. Pre-rendered

Pre-rendering can be optimized for SEO using CSR instead of SSR.

Ix. Project Summary

We built a simple React SSR project, which is still a long way from SSR frameworks like next.js, such as secondary routing, data state management, and asynchronous data loading. Packing and so on. This article aims to understand the relevant knowledge of server-side rendering. In the following articles, I will take you to implement more practical functions based on this project and improve our server rendering framework to reach the commercial level.

Git project address