1. What is it
In SSR (opens new window), we learned about Server-Side Rendering
, referred to as SSR
, which means server-side rendering
Refers to the page processing technology that completes the HTML
structure splicing of the page on the server side, sends it to the browser, and then binds status and events to it to become a fully interactive page.
There are two main problems it solves:
- SEO, since search engine crawlers can view fully rendered pages directly
- Accelerate the loading of the first screen and solve the problem of white screen on the first screen
2. How to do it
In react
, there are two main forms of implementing SSR
:
- Manually build an SSR framework
- Use a mature SSR framework such as Next.JS
This is mainly implemented by manually building a SSR
framework.
First, start a app.js
file through express
to listen for requests on port 3000. When the root directory is requested, HTML
is returned, as follows:
const express = require('express') const app = express() app.get('/', (req,res) => res.send(` <html> <head> <title>ssr demo</title> </head> <body> Hello world </body> </html> `)) app.listen(3000, () => console.log('Exampleapp listening on port 3000!'))
Then write the react
code in the server and reference it in app.js
import React from 'react' const Home = () =>{ return <div>home</div> } export default Home
In order for the server to recognize JSX
, you need to use webpakc
to package and convert the project, create a configuration file webpack.server.js
and make related changes Configuration, as follows:
const path = require('path') //node's path module const nodeExternals = require('webpack-node-externals') module.exports = { target:'node', mode:'development', //development mode entry:'./app.js', //entry output: { //Package export filename:'bundle.js', //Packaged file name path:path.resolve(__dirname,'build') //The build folder stored in the root directory }, externals: [nodeExternals()], //Keep the reference method of require in node module: { rules: [{ //Packaging rules test: /\.js?$/, //Package all js files loader:'babel-loader', //Use babel-loader for packaging exclude: /node_modules/,//Do not package js files in node_modules options: { presets: ['react','stage-0',['env', { //Additional packaging rules for loader, converting react, JSX, ES6 targets: { browsers: ['last 2versions'] //Compatible with the last two versions of mainstream browsers } }]] } }] } }
Then with the help of react-dom
, the renderToString
method of server-side rendering is provided, which is responsible for parsing the React
component into html
import express from 'express' import React from 'react'//Introducing React to support JSX syntax import { renderToString } from 'react-dom/server'//Introduce renderToString method import Home from'./src/containers/Home' const app= express() const content = renderToString(<Home/>) app.get('/',(req,res) => res.send(` <html> <head> <title>ssr demo</title> </head> <body> ${content} </body> </html> `)) app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))
In the above process, the component has been successfully rendered to the page.
However, some event processing methods cannot be completed on the server side, so the component code needs to be executed again in the browser. This way of sharing a set of code between the server side and the client is called isomorphism.
In layman’s terms, refactoring means running a set of React code once on the server, and then running it again on the browser:
- Server-side rendering completes the page structure
- Browser rendering completion event binding
The way the browser implements event binding is to let the browser pull the JS
file for execution and let the JS
code control it, so it is necessary to introduce script
Label
The react
code executed by the client is introduced into the page through the script
tag, and the static
middleware of express
is used for js
file configuration routing, modify as follows:
import express from 'express' import React from 'react'//Introducing React to support JSX syntax import { renderToString } from'react-dom/server'//Introduce renderToString method import Home from './src/containers/Home' const app = express() app.use(express.static('public')); //Use the static middleware provided by express. The middleware will point the routing of all static files to the public folder. const content = renderToString(<Home/>) app.get('/',(req,res)=>res.send(` <html> <head> <title>ssr demo</title> </head> <body> ${content} <script src="/index.js"></script> </body> </html> `)) app.listen(3001, () =>console.log('Example app listening on port 3001!'))
Then execute the following react
code on the client, and create a new webpack.client.js
as the webpack
configuration file of the client React code as follows:
const path = require('path') //node's path module module.exports = { mode:'development', //development mode entry:'./src/client/index.js', //entry output: { //Package export filename:'index.js', //Packaged file name path:path.resolve(__dirname,'public') //The build folder stored in the root directory }, module: { rules: [{ //Packaging rules test: /\.js?$/, //Package all js files loader:'babel-loader', //Use babel-loader for packaging exclude: /node_modules/, //Do not package js files in node_modules options: { presets: ['react','stage-0',['env', { //Additional packaging rules for loader, here convert react and JSX targets: { browsers: ['last 2versions'] //Compatible with the last two versions of mainstream browsers } }]] } }] } }
This method can easily implement react
server-side rendering of the homepage. The process corresponds to the following figure:
After the initial rendering is completed, an application will have routing. The configuration information is as follows:
import React from 'react' //Introducing React to support JSX import { Route } from 'react-router-dom' //Introduce route import Home from './containers/Home' //Introduce Home component export default ( <div> <Route path="/" exact component={Home}></Route> </div> )
Then you can reference the routing information through index.js
, as follows:
import React from 'react' import ReactDom from 'react-dom' import { BrowserRouter } from'react-router-dom' import Router from'../Routers' const App= () => { return ( <BrowserRouter> {Router} </BrowserRouter> ) } ReactDom.hydrate(<App/>, document.getElementById('root'))
At this time, there will be an error message in the console. The reason is that each Route
component is wrapped with a layer of div
, but this div is not included in the code returned by the server.
The solution is to execute the routing information once on the server, use StaticRouter
instead of BrowserRouter
, and pass parameters through context
import express from 'express' import React from 'react'//Introducing React to support JSX syntax import { renderToString } from 'react-dom/server'//Introduce renderToString method import { StaticRouter } from 'react-router-dom' import Router from '../Routers' const app = express() app.use(express.static('public')); //Use the static middleware provided by express. The middleware will point the routing of all static files to the public folder. app.get('/',(req,res)=>{ const content = renderToString(( //Pass in the current path //context is a required parameter, used for server-side rendering parameter transfer <StaticRouter location={req.path} context={<!-- -->{}}> {Router} </StaticRouter> )) res.send(` <html> <head> <title>ssr demo</title> </head> <body> <div id="root">${content}</div> <script src="/index.js"></script> </body> </html> `) }) app.listen(3001, () => console.log('Exampleapp listening on port 3001!'))
This completes the server-side rendering of the route.
3. Principles
The overall react
server-side rendering principle is not complicated, as follows:
node server
receives the client request, gets the current request url
path, then finds the corresponding component in the existing routing table, gets the data that needs to be requested, and Data is passed into the component as props
, context
or store
Then based on react
‘s built-in server-side rendering method renderToString()
, the component is rendered into a html
string and the final html
Data needs to be injected into the browser before outputting
The browser starts rendering and node comparison, and then completes event binding and some interactions within the component. The browser reuses the html
node output by the server, and the entire process ends.