preface

Nowadays, when it comes to the front-end technology stack, MV* frameworks such as VUE and REACT have become the basic front-end knowledge. These frameworks can be used to quickly build single-page applications (SPA), solve the problem of multi-page application resource waste, and improve the development efficiency. But all things have opposites, and SPA brings its own problems: poor SEO and slow first-screen loading. How to solve this problem? The best pioneers came up with a solution: Server-side rendering (SSR).

SSR is actually not a groundbreaking new technology, and before the front and back ends were separated, almost all applications were server-side renderings. SSR can be divided into two modes: single-page mode and non-single-page mode. This article is mainly to understand the single page mode, the VUE single page application into SSR project.

This article will be divided into three parts. The first part will focus on understanding server-side rendering concepts and the problems that can be solved by doing server-side rendering. The second part does a server rendering practice for the project of revamping VUE2. The last part concludes.

1. What is SSR

1.1 define

Let’s take a look at the vue website to explain server side rendering:

Vue.js is a framework for building client applications. By default, the Vue component can be exported to the browser for DOM generation and DOM manipulation. However, it is also possible to render the same component as HTML strings on the server side, send them directly to the browser, and finally “activate” these static tags into a fully interactive application on the client side.

To put it simply, you leave the job of assembling THE HTML to the server, which outputs the HTML string to the browser. As we know, vue-CLI built projects are typically browser rendering (CSR). Let’s compare the process of CSR and SSR.

1.2 the CSR and SSR

The interaction between the client rendering (CSR) and the front-end server is shown in the following figure:

SequenceDiagram Client ->> Server: request, for example visit www.xxx.com/test1 server ->> Client: return HTML, app.bundle.js Note left of client: Render HTML, execute JS client ->> server: interaction, send data request server ->> client: response request, return data

The general process is described as follows:

① First, the browser requests the URL, and the front-end server returns an empty static HTML file (without any database lookup or template assembly) loaded with JavaScript scripts and CSS stylesheets needed to render the page.

② After the browser gets the HTML file, it starts to load the script and style sheet, and executes the script. At this time, it requests the API provided by the back-end service to obtain the data. After obtaining the data, it dynamically renders the data to the page through JavaScript script to complete the page display.

SequenceDiagram Client ->> Server: request, such as access to www.xxx.com/test1 Note right of server: read request, assemble HTML server -->> Client: Return the HTML Note left of the corresponding route: load the first screen HTML

The general process is described as follows:

① The browser requests the URL

According to different URLS, the front-end server requests data from the back-end server. After the request is completed, the front-end server assembles an HTML text with specific data and sends it back to the browser

③ After the browser gets the HTML, it begins to render the page. At the same time, the browser loads and executes JavaScript scripts to bind events to the elements on the page, making the page interactive. When the user interacts with the browser page, such as jumping to the next page, the browser will execute JavaScript scripts. The back-end server requests the data, retrieves the data, and then executes the JavaScript code again to render the page dynamically.

Thus, when the client rendering is performed, the browser is responsible for requesting URL, executing JS scripts and rendering pages. When the server rendering is performed, the browser is not responsible for executing JS scripts to assemble HTML, which reduces the pressure of the browser. At the same time, it should be noted that when jumping to the next page, JavaScript scripts are executed for routing jump. That is to say, the main difference between SPA client rendering and server rendering is the front-screen HTML.

1.3 Problems solved by server rendering

Analysis of the above client-side and server-side rendering shows that there are problems with browser-only rendering:

① The browser is responsible for executing JS, rendering pages, requesting data and a series of work, which will occupy more running memory of the browser, resulting in browser card.

② The server returns empty HTML, which is bad for SEO

③ After the browser gets the HTML, it requests to download JS, CSS, IMG and other resources, and then executes the script to generate the page, resulting in a certain blank screen time, which reduces the user experience.

Server-side rendering has the advantages of better SEO, faster time-to-content, and provides a good solution for browser rendering.

2. VUE2 project was transformed into server rendering

The image above is from the vue server rendering website. As you can see from the figure above, server-side rendering consists of three parts:

  1. The Source. This includes generic application code (in the case of vUE projects, vuE-CLI built projects), Server entry files on the Server side and Client entry files on the Client side.
  2. Webpack build. This includes the server side package configuration file and the client side package configuration file. This step is used to package the project code and export the bundle.
  3. The Node Server. To output the bundle as an HTML string, you need a Node service.

Based on the above, we will reform vuE2 project in three steps.

2.1 The first step is to modify the VUE (main, Router,vuex) and compile server-entry.js and client-entry.js

main, router, vuex

In the non-SSR case, when each user visits the site, the code is downloaded to the client and then rendered independently, so each user’s actions are independent. However, when rendering on the server, all users access the same set of code running on the server side. If you create a singleton, all users will share the instance, which can easily result in cross-request state contamination. As a result, the page requested by the user is not in its current request state. This is clearly unreasonable. Therefore, a new root instance should be created for each request, and we should expose a factory function that can be executed repeatedly.

  1. main.js
export function createApp () { ... Const app = new Vue({router, store, // root instance simple render application component. render: h => h(App) }) return { app, router, store} }Copy the code
  1. router/index.js
const router = new VueRouter({
  mode: 'history', // 
  ...
})

export default function createRouter () {
  return router
}
Copy the code
  1. vuex/index.js
const store = new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
export default function createStore () {
  return store;
}
Copy the code
  1. server-entry.js

Sever -entry.js exposes a function that receives a context, which is the server rendered context object containing information in the server request body. Call this function repeatedly on each render, accept the front-end route, display the contents of the router-view as the corresponding page, and perform the data prefetch logic (asyncData exposed on each component) once the route is ready.

Import {createApp} from './main' // context: address context = req.url {url: Export default context => {// promise return new promise ((resolve, resolve, Reject) => {const {app, router, store} = createApp() router.push(context.url); Will return to the router instances. OnReady (() = > {const matchedComponents. = the router getMatchedComponents () if (! matchedComponents.length) { return reject({ code: 404})} // Call 'asyncData()' promise.all (matchedComponents. Map (Component => {if (component.asyncData) { return Component.asyncData({ store, route: Router.currentroute})}})).then(() => {// After all preFetch hooks resolve, // our store is now populated with the state needed to render the application. // When we attach the state to the context, // and the 'template' option is used for renderer, // the state is automatically serialized to 'window.__INITIAL_STATE__' and injected into HTML. context.state = store.state resolve(app) }).catch(reject) }, reject) }) }Copy the code
  1. client-entry.js

Client-entry.js implements client-side transformations, the process of mounting an application to a root element and turning it into a dynamic DOM managed by Vue. By adding data-server-Rendered =”true” attributes to the root element, the HTML will be marked server rendered, and the HTML will be activated for post-processing and data processing.

Import {createApp} from './main' // The client is activated to attach the contents of the app to the responding page. const { app, router,store } = createApp() router.onReady(() => { app.$mount('#app') })Copy the code

Note: vrouter.onready

2.2 Step 2: Write the package configuration file

The core module is vue-server-renderer. Server-plugin and client-plugin are responsible for packaging server-side and client-side dependencies, respectively.

  1. webpack.server.config.js
const nodeExternals = require('webpack-node-externals') const VueSSRServerPlugin = Module. Exports = {CSS: {// do not extract CSS: false}, outputDir: {// do not extract CSS: false}, outputDir: 'serverDist', configureWebpack: () => ({// server entry: './ SRC /server-entry.js ', devtool: 'source-map', // build target for nodeJS environment target: 'node', // run on node side, so specify package destination (webpack, not write default browser standard) output: {filename: 'server-bundle.js', // build target load mode commonjs libraryTarget: 'commonjs2',}, // skip node_mdoules, runtime load automatically, no need to compile externals: NodeExternals ({// allow CSS files, easy CSS Module allowList: [/\.css$/]}), // Close code cutting optimization: {splitChunks: false }, plugins: [ new VueSSRServerPlugin() ] }) }Copy the code
  1. webpack.client.config.js
Const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') const config = File plug-in. // Default file name: 'vue-ssR-server-bundle. json' configureWebpack: () => ({entry: '.. /src/client-entry.js`, devtool: 'source-map', plugins: [ new VueSSRClientPlugin() ] }), chainWebpack: config => { config.plugins.delete('html'); config.plugins.delete('preload'); config.plugins.delete('prefetch'); } } module.exports = configCopy the code

2.3 Step 3: Build the server code

1.server.js

Main steps:

① Read the Server bundle and client Bundle

② Obtain the createBundleRenderer object of vue-server-renderer

(3) Create a VUE instance by using createBundleRenderer to create a renderer using template, Server Bundle, and Client Bundle.

Renderer’s renderToString method serializes the page and returns the serialized HTML.

const vue = require('vue') const express = require('express'); const server = express() const fs = require('fs') const path = require('path') const serverBundle = require(path.resolve(process.cwd(), 'serverDist', 'vue-ssr-server-bundle.json')); const clientManifestPath = path.resolve(process.cwd(), 'dist', 'vue-ssr-client-manifest.json'); const clientManifest = JSON.parse(fs.readFileSync(clientManifestPath, 'utf-8')); // HTML files are not modular, Const template = fs.readfilesync (path.resolve(__dirname,'index.html'), const template = fs.readfilesync (path.resolve(__dirname,'index.html'), 'utf-8') server.use(express.static(path.resolve(process.cwd(), 'dist'))); Const {createBundleRenderer} = require('vue-server-renderer') // Renderer: generate vue instance const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, clientManifest: clientManifest, template:template }); // const {} = require('./main') // * requires ('./main'). Server.get ('*', (req, res) => {// req request body // res response body if(req.url! == '/favicon.icon') {// context is the context passed to the server-entry const context = {url: req.url, } renderer.renderToString(context).then((html) => { res.end(html) }) } }) server.listen(1000)Copy the code

2.index.html

Vue-ssr-outlet This comment tells the server where to inject HTML.

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta Name ="viewport" content="width=device-width, initial-scale=1.0"> <title> SSR-demo </title> </head> <body> <! --vue-ssr-outlet--> </body> </html>Copy the code

2.4. Supplement vue + nuxtJs for server-side rendering

Nuxt is super easy to render vue server, just use NPX create-nuxt-app app-name to quickly initialize an SSR project. The project catalog is as follows:

  1. Pages folder: put the corresponding pages of routes, a. Vue file represents a route, the file path is the URL, so be careful naming.
  2. Other folders function as vUE projects.
  3. To run it, just follow the script in package.json.

3. Summary

When the user visits the web site rendered by the server, the server passes the URL to the renderer, and the renderer jumps to the corresponding routing component according to the function exposed by the server-client. The HTML is assembled on the server side and returned to the browser. The browser loads the HTML, reducing the time for the browser to execute the script to assemble HTML. The user experience is better, and the returned HTML has more information about the site, which is better for SEO.

Server-side rendering has been a knowledge that appeared a long time ago. Before, I just stayed at the stage of reading the document and never wrote it myself. This time, I just made a basic project according to the document, but there are still many problems in the process.

Finally, with a few seven words quatrain “paper come zhongjue shallow, must know this to practice” always remind yourself.

Source code location: SSR-demo.

reference

  • [1]. [Swastika warning] Thoroughly understand the principle of server rendering SSR from start to finish
  • [2]. In-depth study of SSR and NextJS to reconstruct nuggets!