preface

Wish everyone New Year’s day home single siege lion dating success, has taken off the single to find the opportunity ~~~~

Server-side rendering sounds lofty, in fact, it is so, if the site is not used for commercial purposes, also do not need to be included in the site, it is still good to use the normal ordinary way to write it, unless you want to install force, you can play. The following is my loading force time ~~πŸ™ƒ

Project address: github.com/chenjiaobin…

Why use SSR

  • Speed up the first screen rendering. Because the user from sending a website request to receiving a request, is nothing more than there isJs/CSS download - Request data - page renderingThe difference between these steps, server rendering (SSR) and client rendering (CSR), lies in the sequence of the above steps, as illustrated in the figure belowNote that JS/CSS is downloaded in parallel, but CSS affects JS execution, i.e. JS execution is blocked until the CSS is downloaded and parsed, whereas JS in front of CSS is not. CSS does not affect DOM parsing, but it does affect DOM rendering, because DOM rendering requires the JS DOM and CSS DOM to be combined into Renderdom before rendering. Loading JS files blocks parsing and rendering of DOM and CSS, but does not block parsing of HTML and CSS.
  • It’s good for SEO, because client rendering JS and CSS will block the rendering process of the page, and the final page will be inserted into the DOM through JS, which is what we crawl to<div id="root"></div>, and does not get the specific content of our page, but the server render returns the completevisualPage, that is, not including interaction, interaction needs to wait for the completion of subsequent JS download for binding

Refer to https://www.jdon.com/50088

The client-side rendering and server-side rendering described above actually correspond to two Web building modes: the forward-back split mode and the straight-out mode

  1. Mode 1: Before and after separation mode (corresponding to client rendering)

Both client-side and server-side rendering consist of three principal processes:

  • Download the JS/CSS code
  • The request data
  • To render the page

Client rendering: A -> B -> C (A, B, C are all done on the client)

Server rendering: B -> C -> A (B, C on the server, last A on the client)

Server-side rendering changes the order and side of execution of processes A, B, and C

Technology stack

  • React, React-DOM, redux, etc.
  • Express (Node server)
  • Webpack (Front and back end packaging tool)

Client code

Because the browser is not aware of the new JS usage and react syntax, we need to install webPack to package the source code, so that the code can run in the browser

CNPM I webpack webpack-cli webpack-merge html-webpack-plugin autoprefixer -d // Install Babel to edit ES6 and JSX syntax, CNPM I @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader -d // Used to pack the CSS CNPM I css-loader style-loader postcss-loader -DCopy the code

steps

Run NPM init to create a package.json file with project information, and create build, client, and server folders. Build stores webPack configuration, and client stores client files, i.e. front-end react pages. Server houses the Node server code, which is mainly used for server rendering

  1. Create a new file, webpack-client-config.js, and package the client file for configuration. Here I give the configuration of my project at one time
// merge webpack configuration const merge = require('webpack-merge'Const HTMLplugin = require(const HTMLplugin = require('html-webpack-plugin')
const { resolvePath } = require('./webpack-util') // webpack public configuration file, used for server packaging and client packaging public configuration const baseConfig = require('./webpack-base'Var ExtractTextPlugin = require()"extract-text-webpack-plugin"Module. Exports = merge(baseConfig, {// devTool:'inline-source-map',
  mode: 'development',
  entry: {
    app: resolvePath('.. /client/client.js')
  },
  output: {
    filename: 'js/[name].[hash].js',
    path: resolvePath('.. /dist'// The static resource path will be in /public. The static resource path will be in /public. The static resource path will be in /public. So we need to set a static resource file path to distinguish publicPath:'/public'
  },
  devServer: {
    port: 8060,
    contentBase: '.. /client'// change the contents of the SRC folder to repackage // routinghistoryTherefore, there is a problem that the step route is not cached in the page, the first time to enter the page will not be found, so it can be configured in the development environmenthistoryThe ApiFallback is restoredhistoryApiFallback: true,
  	hot: true,
  	inline: true
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: [/node_modules/],
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [
            { loader: 'css-loader'. // https://stackoverflow.com/questions/57899750/error-while-configuring-css-modules-with-webpack // Syntax of css-loader  options has changedinVersion 3.0.0.localIdentName was moved under modules //option. CSS - Loader3.0.0localThe IndentName attribute has been removed and therefore cannot be written as options: {modules:true, importLoaders: 1, localIdentName: '[name]___[hash:base64:5]'} // You can only write the following options: {modules: {localIdentName: '[name]___[hash:base64:5]' }, importLoaders: 1 } 
            },
            'postcss-loader'
          ]
        })
      }
    ]
  },
  plugins: [
    new HTMLplugin({
      filename: 'index.html',
      template: resolvePath(".. /template.html"}), // Note: NPM install -save-dev extract-text-webpack-plugin@next if there is an error in the package, please reinstall the webpack version. New ExtractTextPlugin(extract-text-webpack-plugin)'./css/[name]-[hash:8].css')]})Copy the code

Basic webpack-config-base.js just go to see the project ha 😁 nothing special

Cs-loader 3.0.0 removes localIdentName from cS-Loader 3.0.0 removes localIdentName from LOADER 3.0.0 Custom style packaging rules) property

misconfiguration

options: { modules: true, importLoaders: 1, localIdentName: '[name]___[hash:base64:5]'
Copy the code

Configured correctly

options: { modules: { localIdentName: '[name]___[hash:base64:5]' }, importLoaders: 1 }
Copy the code
  1. New webpack-util.js, webPack base tool configuration
// get file path const path = require('path')
exports.resolvePath = (filePath) => path.join(__dirname, filePath)
Copy the code
  1. Webpack configuration is not complete, the root directory to create a.babelrc file
{
  "presets": ["@babel/preset-react"]}Copy the code
  1. Start writing the React file and create the client package entry files client.js and app.js under the client folder
// app.js
export default class App extends Component {
  render () {
    return(<div> <p> server render test </p> </div>)}} // client.jsexport class Home extends React.Component {
  render () {
    return (
          <App/>
      </Provider>
    )
  }
}
ReactDom.render(<Home/>, document.getElementById('app'))
Copy the code
  1. At present, it can be packaged normallywebpack --config build/webpack-client-config.jsIf it’s normal, you’ll generate a dist directory in your root directory, if it’s not normal, you can debug it yourself, or pull my project and look at it,portal
  2. Change render to Hydrate at the end of the server rendering, the main differences are as follows

Reactdom.render () clears all children of the DOM node returned from the back end and regenerates them. Reactdom.hydrate (), on the other hand, re-uses children of dom nodes to associate them with virtualDom

Therefore, react16 also abandoned the use of render, and react17 will probably not be able to use reactdom.render () to mix server rendered tags

Server code

  1. Install Node dependencies, mainly Express
  2. Create the index.js executable file in the server directory
import Express from 'express'
import path from 'path'
import { renderToString } from 'react-dom/server'
import fs from 'fs'
import React from 'react'
import { StaticRouter } from 'react-router-dom'
// const App = require('.. /dist/server').default
import App from '.. /client/app'
import { Provider } from 'react-redux'
import createStore  from '.. /client/redux/store'Const server = Express() // static path server.use('/public', Express.static(path.join(__dirname, ".. /dist"))) // This function is mainly used to match the contents of the {{}} tag of the template file and replace it with the data given by our back endfunction templating(props) {
  const template = fs.readFileSync(path.join(__dirname, '.. /dist/index.html'), 'utf-8');
  returntemplate.replace(/{{([\s\S]*?) }}/g, (_, key) => props[ key.trim() ]); } server.use('/', (req, res) => {
  const store = createStore({
    list: {
      list: ['guan yu'.'zhang fei'.'zhaoyun']
    },
    home: {
      title: 'I am a chicken, please enlighten me'}}) const HTML = renderToString(<Provider store={store}> const HTML = renderToString(<Provider store={store}> location={req.url}> <App/> </StaticRouter> </Provider> ) res.send(templating({html,store: JSON.stringify(store.getState())})) }) server.listen('8888', () => {
  console.log('server is started, port is 8888')})Copy the code

Key points:

  • The main difference between a StaticRouter and a BrowserRouter is that we can use JS to get the location in the browser, but not in the Node environment, so the React-Router provides a StaticRouter to let us set the location ourselves
  • Before I start, I have to tell you how to configure the traditional SPA page routing. Let’s take history mode as an example

First we input the URL from the browser, and whatever route your URL matches, the back end gives you index.html. Then we load js to match the corresponding route component and render the corresponding route.

So what is the pattern of our SSR routing?

First of all, we input the URL from the browser, the back end matches the corresponding route to obtain the corresponding routing component, obtain the corresponding data to fill the routing component, and turn the component into HTML and return it to the browser, the browser directly render. At this point, if you click a jump in the page, we will still not send the request, and js will match the corresponding route rendering

  1. Package the server and create webpack-server-pro.js in the build folder as follows
const merge = require('webpack-merge')
const { resolvePath } = require('./webpack-util')
const baseConfig = require('./webpack-base'Critical dependency () : // Critical dependency () : The request of a dependency is an // //expression const nodeExternals = require('webpack-node-externals'Module. Exports = merge(baseConfig, {mode: merge)'production'// To indicate node environment, target must be added:'node', node: {// Use the __filename variable to get the filename of the current module file with the full absolute path __filename:true// Use the __dirname variable to get the full directory name of the current file.true
  },
  context: resolvePath('.. '),
  entry: {
    app: resolvePath('.. /server/index.js')
  },
  output: {
    filename: '[name].js',
    path: resolvePath('.. /dist/server'), // Must be the same as the client's path publicPath:'/public' 
  },
  module: {
    rules: [
      {
        test: /\.css? $/, use: ['isomorphic-style-loader', {
         loader: 'css-loader',
         options: {
          importLoaders: 1,
          modules: {
            localIdentName: '[name]___[hash:base64:5]'
          },
         }
        }]
       }
    ]
  },
  
  externals: [
    nodeExternals()
  ],
})
Copy the code

Note: Isomorphic-style-loader is mainly used to solve the problem of CSS packaging at the server end, because app.js file is introduced at the server end, and there is CSS file application in app.js, then document is not defined error will appear in the packaging at this time. Therefore, we need to solve the problem of CSS files on the server side. At this time, we only need to extract CSS style names to labels, and there is no need to package additional CSS files. Then the package isomorphic-style-loader will come into use. LocalIdentName The customized configuration must be consistent with that on the client. 4. Dist: App.js file, which is the startup file of Node, will be generated in the server folder. We enter the folder from the terminal and execute Node app.js to start our Node service. Provided we pack the front-end code as described in the previous client packaging steps, we can access the back-end rendered page by accessing localhost:8888 in the browser

Packaged deployment

  1. Git clone github.com/chenjiaobin…
  2. NPM install Installs dependencies
  3. NPM Run build package client
  4. NPM Run server-pro-build package server
  5. Go to the server directory packed in step 4 and start the Node service node app.js.
  6. Access localhost: 8888

➀ 🀣, project address github.com/chenjiaobin…

reference

  • www.jianshu.com/p/0ecd72710…
  • Blog.csdn.net/weixin_4295…
  • Blog.csdn.net/weixin_3371…
  • Segmentfault.com/a/119000001…