background

Page opening process

What is server-side render SSR

Before we begin, we need to clarify one question: what is server-side rendering?

In the past, rendering was more done on the client side, so why do we let the server do it now?

Are there any differences between server-side rendering and client-side rendering?

In fact, there are a lot of server-side rendering tools, looking at the manual will soon be able to get started, there is no difficulty, the key lies in what scenarios we need to use server-side rendering, what kind of rendering scheme is more suitable for our project; Learning, know how, we need to understand the service side rendering of the basic concept and principle of the service side rendering why, what is solved our problems, grasp the overall rendering logic and thinking, can we use in learning tools, at ease, and even tools have changed and updated later, we can with ease, No more “can’t learn”;

Render: HTML + CSS + JS + Data -> Render HTML

Server:

All resources such as templates are stored on the server

Intranet machines pull data faster

One HTML returns all the data

Browser and server interaction flow

Client rendering vs server rendering

What is SSR?

Most of the modern front-end projects are single-page applications, which is what we call SPA. The whole application has only one page, and different page contents are displayed by means of components. After all the data is obtained by the request server, the client is assembled and displayed. This is the default rendering logic of the current front-end framework, which we call Client Side Render (CSR for short);

Load the rendering process as follows: HTML/CSS code –> load JavaScript code –> Execute JavaScript code –> render page data

The biggest problem with client-side rendering for SPA applications is twofold:

1: The white screen lasts for a long time, resulting in poor user experience.

2: No content in HTML, SEO is not friendly;

The reason for this problem is that, when loading for the first time, the whole SPA script program needs to be downloaded, and the browser can only obtain the data to be displayed on the page after executing the code logic. However, the download of SPA script requires a long wait and execution time. Meanwhile, the SPA script downloaded to the browser has no page data. The browser actually does not have much rendering work, so users see a page without any content, not only that, because there is no content in the page, the search engine crawler crawls to the blank content, which is not conducive to SEO keyword acquisition;

Compared with the traditional site, browser pages are processed by the server has content static pages, back-end programming experience may be more familiar with some, page structure and content, are processed by the server, returned to the client;

Comparison of the two we will find that traditional site page data synthesis server in the background, and the application of SPA page data synthesis in the browser, but regardless of that, the final rendering, or to the browser to complete, so, don’t get me wrong, we here rendering of a service and the client rendering, refers to the page structure and data synthesis, It’s not a browser presentation job;

So can we use the ideas of traditional websites to solve the problems of SPA and retain the advantages of SPA? Whether long hang time or SEO is not friendly, practical is the first screen page structure back to the browser first, and then to get the data after synthesis problems, so, the first screen page structure and data, as long as like traditional site, first on the server returned again after synthesis, at the same time put the SPA script loading is still in the first screen, At this point, the returned page is the complete content of the structure and data, so that the browser can load the SPA script when displaying the home page data, and the crawler of the search engine can also obtain the corresponding data, to solve the problem of SEO; To better understand this logic, I drew a flow chart:

Yes, this is the basic logic of what we call Server Side Rendering, which is SSR (Server Side Rendering);

The problem of long white screen time is solved because the server returns the rendered static page and reloads the request SPA script in the static page when it is first loaded.

Basic principle: the content and data of the home page are generated into a static page before the user requests, and the script code of SPA is introduced. After the static page is rendered by the browser, SPA script application is requested, and the subsequent page interaction is still client rendering.

The advantage of SSR

Reduce white screen time

Be SEO friendly

SSR code implementation ideas

Webpack SSR packaging problems

Browser global variables (no Document, window in Node.js)

  • Component adaptation: ADAPTS incompatible components based on the packaging environment

  • Request adaptation: Change the writing of fetch or Ajax to isomorphic-fetch or AXIos

Style issues (Node.js can’t parse CSS)

  • · Scheme 1: Server packaging ignores CSS parsing through ignore-loader

  • · Scheme 2: Replace style-loader with isomorphic-style-loader

How to solve the problem that styles are not displayed?

How to process the first screen data?

Webpack SSR configuration

index-server.js

'use strict'; // import React from 'react'; // import largeNumber from 'large-number'; // import logo from './images/logo.png'; // import './search.less'; const React = require('react'); const largeNumber = require('large-number'); const logo = require('./images/logo.png'); const s = require('./search.less'); class Search extends React.Component { constructor() { super(... arguments); this.state = { Text: null }; } loadComponent() { import('./text.js').then((Text) => { this.setState({ Text: Text.default }); }); } render() { const { Text } = this.state; const addResult = largeNumber('999', '1'); return <div className="search-text"> { Text ? <img SRC ={logo} onClick={this.loadComponent.bind(this)} /> </div>; } } module.exports = <Search />;Copy the code

index.html

<! DOCTYPE html> <html lang="en"> <head> ${ require('raw-loader! ./meta.html')} <title>Document</title> <script>${ require('raw-loader! babel-loader! . /.. /node_modules/lib-flexible/flexible.js')}</script> </head> <body> <div id="root"><! - HTML_PLACEHOLDER -- > < / div > < script type = "text/javascript" SRC = "https://11.url.cn/now/lib/16.2.0/react.min.js" > < / script > < script type = "text/javascript" SRC = "https://11.url.cn/now/lib/16.2.0/react-dom.min.js" > < / script > <! --INITIAL_DATA_PLACEHOLDER--> </body> </html>Copy the code

webpack.ssr.js

'use strict'; const glob = require('glob'); const path = require('path'); const webpack = require('webpack'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin'); const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); const setMPA = () => { const entry = {}; const htmlWebpackPlugins = []; const entryFiles = glob.sync(path.join(__dirname, './src/*/index-server.js')); Object.keys(entryFiles) .map((index) => { const entryFile = entryFiles[index]; // '/Users/cpselvis/my-project/src/index/index.js' const match = entryFile.match(/src\/(.*)\/index-server\.js/); const pageName = match && match[1]; if (pageName) { entry[pageName] = entryFile; htmlWebpackPlugins.push( new HtmlWebpackPlugin({ inlineSource: '.css$', template: path.join(__dirname, `src/${pageName}/index.html`), filename: `${pageName}.html`, chunks: ['vendors', pageName], inject: true, minify: { html5: true, collapseWhitespace: true, preserveLineBreaks: false, minifyCSS: true, minifyJS: true, removeComments: false } }) ); }}); return { entry, htmlWebpackPlugins } } const { entry, htmlWebpackPlugins } = setMPA(); module.exports = { entry: entry, output: { path: path.join(__dirname, 'dist'), filename: '[name]-server.js', libraryTarget: 'umd' }, mode: 'none', module: { rules: [ { test: /.js$/, use: [ 'babel-loader', // 'eslint-loader' ] }, { test: /.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader' ] }, { test: /.less$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'less-loader', { loader: 'postcss-loader', options: { plugins: () => [ require('autoprefixer')({ browsers: ['last 2 version', '>1%', 'ios 7'] }) ] } }, { loader: 'px2rem-loader', options: { remUnit: 75, remPrecision: 8 } } ] }, { test: /.(png|jpg|gif|jpeg)$/, use: [ { loader: 'file-loader', options: { name: '[name]_[hash:8].[ext]' } } ] }, { test: /.(woff|woff2|eot|ttf|otf)$/, use: [ { loader: 'file-loader', options: { name: '[name]_[hash:8][ext]' } } ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: '[name]_[contenthash:8].css' }), new OptimizeCSSAssetsPlugin({ assetNameRegExp: /\.css$/g, cssProcessor: require('cssnano') }), new CleanWebpackPlugin(), // new HtmlWebpackExternalsPlugin({ // externals: [ // { // module: 'react', / / entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js', / / global:' react ', / /}, / / {/ / module: 'the react - dom', / / entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js', / / global: 'ReactDOM', // }, // ] // }), new FriendlyErrorsWebpackPlugin(), function() { this.hooks.done.tap('done', (stats) => { if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) {  console.log('build error'); process.exit(1); } }) } ].concat(htmlWebpackPlugins), // optimization: { // splitChunks: { // minSize: 0, // cacheGroups: { // commons: { // name: 'commons', // chunks: 'all', // minChunks: 2 // } // } // } // } stats: 'errors-only' };Copy the code

server/index.js

if (typeof window === 'undefined') { global.window = {}; } const fs = require('fs'); const path = require('path'); const express = require('express'); const { renderToString } = require('react-dom/server'); const SSR = require('.. /dist/search-server'); const template = fs.readFileSync(path.join(__dirname, '.. /dist/search.html'), 'utf-8'); const data = require('./data.json'); const server = (port) => { const app = express(); app.use(express.static('dist')); app.get('/search', (req, res) => { const html = renderMarkup(renderToString(SSR)); res.status(200).send(html); }); app.listen(port, () => { console.log('Server is running on port:' + port); }); }; server(process.env.PORT || 3000); const renderMarkup = (str) => { const dataStr = JSON.stringify(data); return template.replace('<! --HTML_PLACEHOLDER-->', str) .replace('<! --INITIAL_DATA_PLACEHOLDER-->', `<script>window.__initial_data=${dataStr}</script>`); }Copy the code

Mature SSR solution

The React framework corresponds to the Next. Js framework, and the Vue framework corresponds to the Nuxt.js framework. Of course, if you are not interested in these, you can also implement an SSR server application. I wrote one myself before, if you are interested, want to see my implementation of the code, you can leave a message to me, back to make a tutorial sent;

Take React’s Next. Js as an example to get a feel for server rendering.

Next, js framework

Chinese official website homepage, has been introduced very clearly: www.nextjs.cn/

The basic application

Install the deployment

Project installation command: NPX create-next-app or NPM init next-app my-next-project

Run the NPM run dev command to start the project

You can also view the script configuration in the./package.json file

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}
Copy the code

These scripts involve different stages of application development:

  • Dev – Run next dev to start next.js in development mode

  • Build – Run next Build to build the application for production

  • Start – Running next start will start the next. Js production server

Check out our application at http://localhost:3000.

Page routing

In next.js, pages are Reac components placed in the Pages folder, as defined by the framework;

Components need to be exported by default; React is not needed in component files; The page address is corresponding to the file address;

A page is associated with a route based on its file name. For example, pages/about.js is mapped to /about.

Create an abou.js in the Pages directory of your project.

Fill the./pages/about.js file with the following:

Import react from 'react' class AboutPage extends React.Component {render(){return <h3>class Component -- About Page</h3> } } export default AboutPageCopy the code

React function AboutPage(){return <h3> function Component -- AboutPage </h3>} export default AboutPageCopy the code

Page jump

By default, the Link component uses Javascript for page jumps, that is, spa-style jumps. If Javascript is disabled in the browser, the Link component should not add attributes other than href. The Link component automatically optimizes the application for best performance through prefetching (in production) functionality

React function AboutPage() {return (<div> {/* Link must write a tag, <Link href="/list"><a>Go to list Page</a></Link> <h3>Function Component</h3> </div>)} export default AboutPage</pre>Copy the code

Static resource

The public folder in the application root is used to hold static resources; PNG public/ CSS /style. CSS ->/ CSS /style.css

Import React from 'React' class ListPage extends React.Component {render(){return <div> <h3>ListPage</h3> {/* */} <img src="/img/1.png" /> </div> } } export default ListPage</pre>Copy the code

CSS styles

Built-in styled – JSX

Github.com/vercel/styl…

Soft-jsx is built into next.js, which is a CSS-in-JS library that allows writing CSS in the React component. CSS acts only inside the current component;

import React from 'react' class ListPage extends React.Component { render(){ return <div> <h3 >ListPage</h3> <p </p> <style JSX > {'. Pra {color: red; } `} </style> </div> } } export default ListPage</pre>Copy the code
CSS module

By using the CSS module function, the CSS styles of components are allowed to be written in a separate CSS file. The name of the CSS module convention style file must be: component file name.module.css

Create the./pages/list.module. CSS file and fill in the following contents:

.prag{ color:brown; font-size: 20px; } import React from 'React' import ListStyle from './list.module. CSS 'class ListPage extends React.Component { Render (){return <div> <h3 >ListPage</h3> {/* use style */} <p className={liststyle. prag}> export default ListPage</pre>Copy the code
Global style file

1: create a _app.js file in the pages folder (the file name is fixed) 2: create a styles folder in the root directory and create global. CSS in it 3: import global. CSS in _app.js

Styles in global.css will take effect globally

The contents of the /pages/_app.js file are as follows:

import '.. // styles/globals. CSS '// fixed code function MyApp({Component, pageProps}) {return <Component {... pageProps} /> } export default MyAppCopy the code

In the new version of the framework, has helped us to do a good job of the relevant content and files and code

Server-side rendering methods

GetServerSideProps () this method is a server-side rendering method suitable for applications where page data is changing in real time; The getServerSideProps() method takes a parameter that is the current HTTP client request object;

Import React from 'React' class ListPage extends React.Component {render(){return <div> <h3 >ListPage</h3> < H4 > Server data: < / h4 > {/ * class components display data content * /} < p > {this. Props. Data [0]. Name} < / p > < / div >}} / / / / function component/function/ListPage ({data}) {/ / Return (/ / < div > / / < h3 > ListPage < / h3 > / / < h4 > server data: < / h4 > / / {/ * class components display data content * /} / / < p > {data [1]. The name} < / p > < / div > / / / / /} / / the Node implementation of environment / / file reading and writing, database links, // export Async function getStaticProps(){export async function getServerSideProps(){const res = await fetch('http://localhost:80/'); const backData = await res.json(); const data = JSON.parse(backData); console.log(data); return { props:{data} } } export default ListPageCopy the code

Project construction and operation

NPM Run Build

Start project: NPM run start

Static site generation

Next. Js provides not only server-side rendering, but also static site generation solutions;

Static Site generation (also known as SSG (Static Site Generators)) is a Static file generation scheme for all pages used in an application.

Next official recommendation in most cases to use static site generation, static site generation scheme, more suitable for CDN, cache, content data unchanged pages, such as: publicity pages, blog articles, help documents, news pages, e-commerce product list and many other application scenarios;

GetStaticProps and getStaticPaths in next. js are static site generation; Is the method of generating HTML at build time, and each subsequent request shares the HTML generated at build time;

Next. Js recommends static generation for most pages because pages are pre-generated, built once, reused repeatedly, and accessed quickly. Server-side rendering access is not as fast as static generation, but because each request will be re-rendered, it is suitable for pages with frequently updated data or pages whose content changes with requests;

In next. Js, static generation is divided into two cases: no data and data; If the component does not need to fetch data elsewhere, it is generated statically by default. If the component needs to fetch data elsewhere, at build time, next.js will pre-fetch the data the component needs and then generate the component statically

For comparison, the development environment does not package static files, while the production environment packages static files by default

So what do you do when you have data?

There is static generation of data

GetStaticProps () This method is officially translated as static generation. Components are pre-compiled into HTML files, and the entire HTML file is responded to the client, so as to achieve the purpose of pre-rendering.

The getStaticProps() method is an asynchronous method that executes in a Node environment (at build time), so it can do file reads and writes, database links, network communications, and more

For the use of this method, see demo:

Import React from 'React' import Axios from "Axios" // Class ListPage extends React.Component {render(){return <div> <h3 >ListPage</h3> <h4> Server data: < / h4 > {/ * class components display data content * /} < p > {this. Props. Data [0]. Name} < / p > < / div >}} / / / / function component/function/ListPage ({data}) {/ / Return (/ / < div > / / < h3 > ListPage < / h3 > / / < h4 > server data: < / h4 > / / {/ * class components display data content * /} / / < p > {data [1]. The name} < / p > < / div > / / / / /} / / the Node implementation of environment / / file reading and writing, database links, Export Async function getStaticProps(){const d3 = await axios.get ('http://localhost:80/'); const data = JSON.parse(d3.data); Console. log(data) // The value of the Props property returned is passed to the component return {Props :{data}}} export default ListPageCopy the code

The getStaticProps method must return an object within which the props property is passed to the component.

GetStaticPaths () This method is also statically generated. This method is used with getStaticProps to generate different static pages for different request parameters. The code file must be stored in a directory and the file name of the code file must be in the form of an optional file name, such as \ Pages \props\[id].js. When the project is built, Next will generate different static files based on different ID values, as shown in the following code

Import React from 'React' import Axios from "Axios" // Class ListPage extends React.Component {render() {return <div> <h3 >ListPage - Class</h3> <p>{this.props. Backdata. name}</p> </div>}} // Generate a static page based on the client parameters export Async function Paths: [{params: {id: "1"}}, {params: {id: "2"}}], fallback: False}} // Automatically call the current method, passing in the client arguments; Export Async function getStaticProps({params}) {const d3 = await Axios.get('http://localhost:80/'); const data = JSON.parse(d3.data); Console. log(params) // Loop server data, get for (let I = 0; i < data.length; I ++) {// console.log(data[I].id) if (params.id == data[I].id) {const backData = data[I]; return { props: { backData } } } } } export default ListPageCopy the code

After the final build, different static pages are generated:

Static Site Export

"scripts": {
 "dev": "next dev",
 "build": "next build",
 "start": "next start",
 "export": "next build && next export"
 },
Copy the code

Run the NPM run export command to build and export data, and generate the OUT folder to obtain static site resources.

In addition, there is the SSG static site generation solution for React: Gatsby www.gatsbyjs.cn/. If you are interested, check it out for yourself

Of course, you React have it, how could I Vue not: Gridsome www.gridsome.cn/