Write in front of the article

Due to the variety of front-end scaffolding, packaging tools, Node, and other versions, this article cannot be done at the same time, and is based on the following technology stack.

Scaffolding: VUE-CLI3

Package tool: Webpack4, which is integrated in vue-cli3 and configured by modifying vue.config.js

Node framework: KOA2

Introduction to the

Server-side rendering, that is, the “isomorphism” strategy is adopted to render part of the front-end code on the server side to reduce the amount of browser rendering to the page.

In general, server-side rendering has the following advantages and uses:

1. Better SEO

2. Faster page loading speed

3. Load data on the server

However, it is important to note that while server-side rendering improves client performance, it also introduces the problem of higher server load. The pros and cons need to be weighed during project development.

How to implement server-side rendering in Vue project?

Some thoughts before doing VUE-SSR

1.Vue takes Vue instance as the basic unit during page rendering. Should Vue instance also be rendered during server rendering?

2. The relationship between the user and the client is one-to-one, but the relationship between the user and the server is many-to-one. How to avoid data sharing between multiple users on the server?

3. How to implement isomorphism strategy? So that the server can run the front-end code?

4. How should the development environment and production environment be deployed for the Vue project rendered on the server side? What’s the difference?

5. How to ensure that the code after server-side rendering transformation can still be directly accessed by accessing static resources?

These reflections will be reviewed at the end of this article.

Concrete implementation scheme

Vue officially provides [vue-server-renderer] package to implement Vue project server rendering, installation is as follows:

npm install vue-server-renderer --save
Copy the code

Here are some things to be aware of when using vue-server-renderer:

1. Vue-server-renderer version must be consistent with VUE

2. Vue-server-renderer can only be run on node. Node.js6 + is recommended

One, the simplest implementation

Vue -server-renderer provides a createRenderer method for rendering a single vue instance and output a rendered HTML string or a Stream that can be read by Node.

// 1. Create a Vue instance
const Vue = require('vue');
const app = new Vue({
  template: '<div></div>'});// 2. Introduce renderer methods
const renderer = require('vue-server-renderer').createRenderer();

// 3-1. Render Vue instance as HTML string
renderer.renderToString(app, (err, html) => {});
// or
renderer.renderToString(app).then((html) = > {}, (err) => {});

// 3-2. Render the Vue instance as a stream
const renderStream = renderer.renderToStream(app);
// Operate in a callback by subscribing to events
// event can be 'data', 'beforeStart', 'start', 'beforeEnd', 'end', 'error', etc
renderStream.on(event, (res) => {});
Copy the code

However, in general, it is not necessary to create Vue instances and render them on the server side. Instead, we need to render each SPA Vue instance in the front-end Vue project. Based on this, VUe-server-renderer provides us with a set of server-side rendering schemes as follows.

Second, complete implementation

As shown in the figure below, the complete implementation process is divided into three modules: [Template page] (HTML), [Client Bundle] and [Server Bundle]. The functions of the three modules are as follows:

Template page: AN HTML framework for client and server rendering in which to render pages

Client: Injects static resources, such as JS and CSS, into the template page only on the browser

Server side: Executed only on the server side, the Vue instance is rendered as an HTML string and injected into the appropriate location on the template page

The entire service construction process is divided into the following steps:

1. Package the Vue application into a client Bundle that can be executed on the browser side through Webpack;

2. Use Webpack to package Vue applications into Node side bundles that can be executed;

3. The Node side calls the server side Bundle to render the Vue application, and sends the rendered HTML string and the client Bundle to the browser;

4. After receiving the message, the browser invokes the client Bundle to inject static resources into the page and matches the rendered page on the server.

It should be noted that the content rendered by the client and the server must match to properly load the page. Some page loading exceptions will be described in details in the following sections.

Three, specific code implementation

1. Vue application program transformation

In SPA mode, the relationship between users and Vue application is one-to-one, while in SSR mode, the relationship between users and Vue application becomes many-to-one as Vue instance is rendered on the server, which is shared by all users. This results in multiple users sharing the same Vue instance, causing data in the instance to contaminate each other.

To solve this problem, we need to change the entry of Vue application, change the creation of Vue instance to “factory mode”, create a new Vue instance every time rendering, avoid the situation of users sharing the same Vue instance. The specific transformation code is as follows:

// router.js
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

export function createRouter() {
    return new Router({
        mode: 'history'.routes: [],}); }// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export function createStore() {
    return new Vuex.Store({
        state,
        actions: {},
        mutations: {},
        modules: {},}); }// main.js
import Vue from 'vue';
import App from './App.vue';
import {createRouter} from './router';
import {createStore} from './store';

export function createApp() {
    const router = createRouter();
    const store = createStore();

    const app = new Vue({
        router,
        store,
        render: (h) = > h(App),
    });
    return {app, router, store};
}
Copy the code

It should be noted that modules used internally by VUE instances such as VUE-Router and VUex should also be configured as “factory mode” to avoid sharing routes and status among multiple VUE instances.

At the same time, because we need to use the client and server modules in the SSR process, we need to configure the client and server two entrances.

The client configuration is as follows:

// entry-client.js
import {createApp} from './main';

const {app, router, store} = createApp();

if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__);
}

router.onReady((a)= > {
    app.$mount('#app');
});
Copy the code

As mentioned above, the function of the client Bundle is to inject static resources into the page and render the page again after the browser receives the HTML string rendered by the server. Therefore, in the client entry of the Vue application, we only need to mount the Vue instance to the specified HTML tag as before.

At the same time, if there is a data prefetch operation on the server side, the store data will be injected into [window.__initial_state__]. On the client side, we need to reassign the value of window.__initial_state__ to the store.

The server entrance configuration is as follows:

// entry-server.js
import {createApp} from './main';

export default (context) => {
    return new Promise((resolve, reject) = > {
        const {app, router, store} = createApp();

        // Set the router location on the server
        router.push(context.url);

        // Wait until the router has resolved possible asynchronous components and hook functions
        router.onReady((a)= > {
            const matchedComponents = router.getMatchedComponents();
            Reject if the route cannot be matched, reject, and return 404
            if(! matchedComponents.length) {return reject({
                    code: 404
                });
            }
            Promise.all(matchedComponents.map((Component) = > {
                if (Component.extendOptions.asyncData) {
                    const result = Component.extendOptions.asyncData({
                        store,
                        route: router.currentRoute,
                  			options: {},});return result;
                }
            })).then((a)= > {
                // The state is automatically serialized to window.__initial_state__ and injected with HTML.
                context.state = store.state;
                resolve(app);
            }).catch(reject);
        }, reject);
    });
};
Copy the code

The server needs to dynamically match the Vue components to be rendered according to the user’s request, and set up modules such as router and Store.

For router, just call vue-Router’s push method to switch routes.

For store, it is necessary to detect and call the [asyncData] method in the Vue component to initialize the store and assign the initialized state to the context. The server will serialize the state in the context to window.__INITIAL_STATE__ during rendering. And inject it into HTML. The operations and processing of data prefetch are described in server-side data prefetch in the following section.

2. Webpack packages logical configuration

Since the server-side rendering service requires two packages, the client Bundle and the server Bundle, it needs to use WebPack to package the client and server side respectively. Here we can use shell script to write the packaging logic:

#! /bin/bashSet -e echo "Delete old DIST file" rm -rf dist echo "Pack SSR server" export WEBPACK_TARGET=node && vue-cli-service build echo Dist "mv dist/ vue-ssR-server-bundle. Json bundle echo" export WEBPACK_TARGET=web && Vue-cli-service build echo "dist" mv bundle dist/vue-ssr-server-bundle. JsonCopy the code

In the shell command, we configure the [WEBPACK_TARGET] environment variable to provide Webpack with identification of the client/server side packaging process.

At the same time, vue-server-renderer provides two Webpack plugins [server-plugin] and [client-plugin], which are used to package the server side and the client side bundles respectively. Here are the configuration details in the WebPack configuration file that are packaged using these two plug-ins:

// vue.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin');
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin');
const merge = require('lodash.merge');

const TARGET_NODE = process.env.WEBPACK_TARGET === 'node';
const entry = TARGET_NODE ? 'server' : 'client';
constisPro = process.env.NODE_ENV ! = ='development';

module.exports = {
  	* During local development, to ensure normal loading of static resources, start a static resource server on port 8080. * This process is described in detail in section 4 "Node Development Environment Configuration" */
    publicPath: isPro ? '/' : 'http://127.0.0.1:8080/'.outputDir: 'dist'.pages: {
        index: {
            entry: `src/pages/index/entry-${entry}.js`.template: 'public/index.html'}},css: {
        extract: isPro ? true : false,},chainWebpack: (config) = > {
      	// Disable the default server rendering function in vue-loader
        config.module
            .rule('vue')
            .use('vue-loader')
            .tap((options) = > {
                merge(options, {
                    optimizeSSR: false}); }); },configureWebpack: {
      	// Source-map file mapping needs to be enabled because the server side is rendering,
      	// Files will be queried through the Bundle's map file mapping
        devtool: 'source-map'.// The server runs in the Node.js environment and needs to be packaged as a node.js environment available package (using node.js require loading chunk)
      	// The client runs in the browser and needs to be packaged as a package available in the browser-like environment
        target: TARGET_NODE ? 'node' : 'web'.// Turn off polyfill for node variables and modules
        node: TARGET_NODE ? undefined : false.output: {
          	Module. exports is used on the server and var is used on the client by default
            libraryTarget: TARGET_NODE ? 'commonjs2' : undefined,},// Externalize application dependency modules. Can make server builds faster
        externals: TARGET_NODE ? nodeExternals({
            whitelist: [/\.css$/]}) :undefined.plugins: [
          	// Package as client/server Bundle based on environment variables previously configured
            TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin(),
        ],
    },
};
Copy the code

With the code and comments from the WebPack configuration file, let’s go back to the packaged shell script to comb through the packaging process.

1) Package the server Bundle

First, set the [WEBPACK_TARGET] environment variable to Node, and Webpack will set the entry to the server-side entry [entry-server.js], which is packaged by the plug-in [server-plugin].

After packaging, the vue-SSR-server-bundle. json file (the default name, which can be set in the plug-in) will be generated in the dist folder. The file has three attributes entry, files, and maps. Where the entry attribute is the string of the packaged entry file path, the files attribute is a group of packaged [file path – file content key-value pair], the content of the compiled file will be stored in the files attribute of the JSON file. Maps, on the other hand, are a set of file resource configurations compiled from source-Map.

// vue-ssr-server-bundle.json
{
  	"entry": "js/index.[hash].js",
  	"files": {
      	"js/index.[hash].js": "",
    },
  	"maps": {
      	"js/index.[hash].js": {}
    }
}
Copy the code
2) Temporarily remove the packaged files from the dist folder on the server side

Due to the need for packaging twice, the previous Dist folder will be deleted when packaging the client. In order to avoid the loss of the server Bundle, it needs to be temporarily moved out of the dist folder.

3) Package the client Bundle

When packaging the client, change the [WEBPACK_TARGET] environment variable to Web, and Webpack will set the entry to the client entry [entry-client.js], which is packaged by the plug-in [client-plugin].

After packaging, static resource files of front-end projects packaged and [vue-SSR-client-manifest.json] files will be generated in the dist folder. The static resource files can be deployed to the server to provide traditional SPA services. Json file contains publicPath, all, initial, async and modules properties, whose functions are as follows:

PublicPath: root relative path for accessing static resources, consistent with publicPath in webPack configuration

All: indicates the path of all static resource files after packaging

Initial: The file that needs to be loaded when the page is initialized and configured in preload when the page is loaded

Async: a file that needs to be loaded during page hopping. The file is configured in Prefetch during page loading

Modules: The sequence number of files that each module of the project contains, corresponding to the order of files in all

// vue-ssr-client-manifest.json
{
  	"publicPath": "/",
  	"all": [],
  	"initial": [],
  	"async": [],
  	"modules": {
      	"moduleId": [
          	fileIndex
        ]
    }
}
Copy the code
4) Move the server Bundle temporarily removed from Dist back to the dist folder
3. Configure the production environment on the Node end

After the above steps of packaging process, we have packaged the project into three parts: [VUe-SSR -server-bundle.json], [VUe-SSR -client-manifest.json] and [front-end static resources]. We then need to use the packaged contents of the three modules on the Node side for server-side rendering.

1) Differences between Renderer and BundleRenderer

There are two main classes for server-side rendering, Renderer and BundleRenderer.

In the simplest implementation section, we mentioned the createRenderer method, which essentially creates a Renderer object for rendering. This object contains renderToString and renderToStream methods. Use to render a Vue instance as an HTML string or to generate a Node readable stream.

In the section of [Complete Implementation], we used the method of packaging the project as client and server Bundle. In this case, we need to use another method of vue-server-renderer [createBundleRenderer]. Create BundleRenderer objects for rendering.

CreateBundleRenderer /build.dev. Js createBundleRenderer
function createBundleRenderer(bundle, rendererOptions) {
  if ( rendererOptions === void 0 ) rendererOptions = {};

  var files, entry, maps;
  var basedir = rendererOptions.basedir;

  // load bundle if given filepath
  if (
    typeof bundle === 'string' &&
    /\.js(on)? $/.test(bundle) &&
    path$2.isAbsolute(bundle)
  ) {
    // Parse the bundle
  }

  entry = bundle.entry;
  files = bundle.files;
  basedir = basedir || bundle.basedir;
  maps = createSourceMapConsumers(bundle.maps);

  var renderer = createRenderer(rendererOptions);

  var run = createBundleRunner(
    entry,
    files,
    basedir,
    rendererOptions.runInNewContext
  );

  return {
    renderToString: function (context, cb) {
      run(context).catch((err) = > {}).then((app) = > {
        renderer.renderToString(app, context, (err, res) => {
          cb(err, res);
        });
      });
    },
    renderToStream: function (context) {
      run(context).catch((err) = > {}).then((app) = >{ renderer.renderToStream(app, context); }); }}}Copy the code

RenderToString (createBundleRenderer) and renderToStream (createBundleRenderer); It receives the Bundle file or file path on the server side. During execution, it checks whether the object or string is received. If it is a string, it reads the file as a file path. After the Bundle file is read, the properties of the server-side Bundle described in the Webpack Logic Configuration section are resolved. Construct the Renderer object at the same time, calling the renderToString and renderToStream methods of the Renderer object.

As you can see, the only difference between BundleRenderer and Renderer is the additional Bundle resolution process, and then Renderer is still used for rendering.

2) Code implementation

Now that we know the difference, we’ll use the BundleRenderer object for server-side rendering here, as follows:

// prod.ssr.js
const fs = require('fs');
const path = require('path');
const router = require('koa-router') ();const resolve = file= > path.resolve(__dirname, file);

const { createBundleRenderer } = require('vue-server-renderer');
const bundle = require('vue-ssr-server-bundle.json');
const clientManifest = require('vue-ssr-client-manifest.json');
const renderer = createBundleRenderer(bundle, {
    runInNewContext: false.template: fs.readFileSync(resolve('index.html'), 'utf-8'),
    clientManifest,
});

const renderToString = (context) = > {
    return new Promise((resolve, reject) = > {
        renderer.renderToString(context, (err, html) => {
            err ? reject(err) : resolve(html);
        });
    });
};

router.get(The '*'.async (ctx) => {
    let html = ' ';
    try {
        html = await renderToString(ctx);
        ctx.body = html;
    } catch(e) {}
});

module.exports = router;
Copy the code

In the code, you can see that the entire rendering process is divided into three steps:

1. Obtain the server, client, and template files, and use createBundleRenderer to create a BundleRenderer object.

2. Upon receiving the user request, the renderToString method is called and the request context is passed in. At this time, the server-side rendering service will call the server-side entry file entry-server.js for page rendering;

3. Configure the rendered HTML string into the body of the response and return it to the browser.

4. Configure the Node development environment

Vue officially only provides a solution for server-side rendering of Vue instances and packaged bundles, but in the development environment we face the following problems:

1) Webpack stores the packaged resource files in memory. How do I get the JSON files of the packaged Bundle?

2) How to package and run both client and server in the development environment?

Here, our strategy is to use Webpack to start the front-end project of the development environment and obtain the client static resource [vue-SSR-client-manifest.json] in memory through HTTP request. Meanwhile, in Node, use [@vue/ CLI-service /webpack.config] to obtain the webpack configuration on the server side, and use webpack package to package the Bundle on the server side directly. Json file (vUE – SSR -server-bundle.json) From there, we get the client and server files, and the process is the same as in production.

First, let’s look at the configuration of the NPM command:

// package.json
{
		"scripts": {
        "serve": "vue-cli-service serve",
        "server:dev": "export NODE_ENV=development WEBPACK_TARGET=node SSR_ENV=dev && node --inspect server/bin/www",
        "dev": "concurrently \"npm run serve\" \"npm run server:dev\" "
    }
}
Copy the code

The serve command uses the client mode to start the front-end service. Webpack will package the client Bundle in the development environment and store it in the memory.

The server:dev command obtains the webpack configuration of the server Bundle in the development environment by setting the environment variables [NODE_ENV] and [WEBPACK_TARGET]. The node application identifies the current environment as the development environment by setting the environment variable [SSR_ENV].

The dev command is the running command of the development environment, and serve and server:dev are concurrently executed by two processes of the concurrently command.

Next, let’s look at the server-side rendering service code for the development environment:

const webpack = require('webpack');
const axios = require('axios');
const MemoryFS = require('memory-fs');
const fs = require('fs');
const path = require('path');
const Router = require('koa-router');
const router = new Router();
// Webpack configuration file
const webpackConf = require('@vue/cli-service/webpack.config');
const { createBundleRenderer } = require("vue-server-renderer");
const serverCompiler = webpack(webpackConf);
const mfs = new MemoryFS();
serverCompiler.outputFileSystem = mfs;

// Monitor file changes, real-time compile to get the latest vue-ssR-server-bundle. json
let bundle;
serverCompiler.watch({}, (err, stats) => {
    if (err) {
        throw err;
    }
    stats = stats.toJson();
    stats.errors.forEach(error= > console.error(error));
    stats.warnings.forEach(warn= > console.warn(warn));
    const bundlePath = path.join(
        webpackConf.output.path,
        'vue-ssr-server-bundle.json',); bundle =JSON.parse(mfs.readFileSync(bundlePath, 'utf-8'));
    console.log('New bundle generated.');
})

const handleRequest = async ctx => {
    if(! bundle) { ctx.body ='Wait for webpack to complete before accessing';
        return;
    }

    // Get the latest vue-ssr-client-manifest.json
    const clientManifestResp = await axios.get(`http://localhost:8080/vue-ssr-client-manifest.json`);
    const clientManifest = clientManifestResp.data;

    const renderer = createBundleRenderer(bundle, {
        runInNewContext: false.template: fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8'),
        clientManifest,
    });

    return renderer;
}

const renderToString = (context, renderer) = > {
    return new Promise((resolve, reject) = > {
        renderer.renderToString(context, (err, html) => {
            err ? reject(err) : resolve(html);
        });
    });
};

router.get(The '*'.async (ctx) => {
    const renderer = await handleRequest(ctx);
    try {
        const html = await renderToString(ctx, renderer);
        console.log(html);
        ctx.body = html;
    } catch(e) {}
});

module.exports = router;
Copy the code

It can be seen from the code that the Node server-side rendering service process in the development environment is basically the same as that in the production environment, and the difference lies in the different acquisition methods of the client and the server Bundle.

In a production environment, Node reads locally packaged static resources directly;

In a development environment, an HTTP request is first sent using AXIOS to retrieve the client Bundle packaged in memory by the front-end project. NODE_ENV=development WEBPACK_TARGET=node SSR_ENV=dev; Run the server side directly in the current Node program using the Webpack package and the Webpack configuration and get the server side Bundle from it.

The subsequent process is the same as the production environment.

5. Node applies configuration

At this point, we have configured the basic files required for server-side rendering and, of course, a Node application to start the service.

// app.js
const Koa = require('koa');
const app = new Koa();
const path = require('path');
const koaStatic = require('koa-static');
const koaMount = require('koa-mount');
const favicon = require('koa-favicon');

const isDev = process.env.SSR_ENV === 'dev';

// routes
const ssr = isDev ? require('./dev.ssr') : require('./prod.ssr');

// Static File Server
const resolve = file= > path.resolve(__dirname, file);
app.use(favicon(resolve('./favicon.ico')));
app.use(koaMount('/', koaStatic(resolve('.. /public'))));

app.use(ssr.routes(), ssr.allowedMethods());

module.exports = app;
Copy the code

In the node entry file, determine whether the current environment is a development environment or a production environment according to the environment variable [SSR_ENV], and call the corresponding server-side render file.

Note that if the publicPath configured in WebPack is a relative path, the browser will access static resources based on the current domain name /IP after the client injects static resources in the relative path to the page. If the server has not done any other proxy (proxy other than the Node service), these static resource requests will be directly transmitted to our Node application. The most convenient way is to build a static resource server in the Node application to proxy the static resources (JS, CSS, PNG, JPG, etc.) after the project is packaged. The koA-mount and KOa-static middleware are used here. It is also possible to mount the Favicon.ico icon using the koa-Favicon middleware.

Data prefetch on the server

Server-side data prefetch is a function that injects data into a Vue instance during server rendering of a Vue application. It is commonly used in the following situations:

1. A large amount of data during page initialization affects the loading speed of the first screen

2. Some data cannot be obtained on the browser

For data prefetch, the solution provided by the official vue-server-Renderer package is divided into two steps:

1. Prefetch data on the server

Data prefetch on the server is mainly aimed at the problem that the first screen load is slow due to slow data reading on the client. It is to inject data into the store of Vue instance after the server-side Vue instance rendering is completed. The code can be reviewed in the section “Vue application Transformation”, and the specific process is as follows:

1) Change store to factory mode, which has been described above and will not be repeated;

2) Register the static method asyncData in the vUE instance and provide it to the server for calling. The function of this method is to call the action method in the store and call the interface to obtain data;

// Vue component file
export default Vue.extend({
  	asyncData({store, route, options}) {
        return store.dispatch('fetchData', { options, }); }});Copy the code

3) Call asyncData method in [entry-server.js] to get data and store data in [window.__INITIAL_STATE__], which can be seen in the [entry-server.js] file configuration above;

4) Re-mount the data in [window.__INITIAL_STATE__] to store in the [entry-client.js] client.

// entry-client.js
const {app, router, store} = createApp();

if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__);
}

router.onReady((a)= > {
    app.$mount('#app');
});
Copy the code

2. Prefetch data from the client

Data prefetch on the client is a supplement to data prefetch on the server. For scenarios, after the server delivers the rendered page to the browser, the work of route switching is also taken over by the vUE virtual route of the browser, and no page request is sent to the server. As a result, data prefetching on the server is not triggered after switching to a new page.

To solve this problem, the client data prefetch policy is to perform operations in the entry-client.js client. When route switchover is detected, data prefetch is performed first (in fact, the data prefetch process on the server is replicated on the client).

Specifically, we need to modify [Entry-client.js] :

// entry-client.js
const {app, router, store} = createApp();

if (window.__INITIAL_STATE__) {
    store.replaceState(window.__INITIAL_STATE__);
}

router.onReady((a)= > {
  	router.beforeResolve((to, from, next) = > {
        const matched = router.getMatchedComponents(to);
        const prevMatched = router.getMatchedComponents(from);

        // Find two different components that match the list without repeating the data read
        let diffed = false
        const activated = matched.filter((c, i) = > {
          	returndiffed || (diffed = (prevMatched[i] ! == c)); });if(! activated.length) {return next();
        }

        Promise.all(activated.map(c= > {
            if (c.extendOptions.asyncData) {
                return c.extendOptions.asyncData({
                    store,
                  	route: to,
                  	options: {},}); } })).then((a)= > {
          	next();
        }).catch(next);
    })
    app.$mount('#app');
});
Copy the code

Matters needing attention

1. Abnormal page loading

After the HTML string rendered by the server is sent to the browser, the client needs to match its template. If the match fails, the page cannot be normally rendered. Therefore, abnormal page loading may occur in some cases, mainly including the following types.

1. The template page lacks a rendering identifier that can be identified by the client or server

This problem can affect static resource injection on the client side or server-side rendering of Vue instances. For the client, it generally needs recognizable H5 tag elements to mount. In this paper, a DIV tag with the ID of APP is used. For the server side, an official vue-server-Renderer package recognisable annotation identifier is required, i.e. The complete template page code is as follows:

// index.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>Page template</title>
  </head>
  <body>
    <div id="app"><! --vue-ssr-outlet--></div>
  </body>
</html>
Copy the code

2. The routes on the client and server are different

Route mismatches occur when the user sends a/A route page request to the server, and the server renders the component corresponding to the/B route as an HTML string and returns it to the browser. When the client detects that the rendered Vue route is inconsistent with the route in the current browser, the page is switched to the page under the/A route, causing the page to be refreshed twice.

3. Static resource loading fails

Because browsers request static resources based on the current domain name /IP when static resources on a page use relative paths, they request static resources from our Node service. If we only do server-side rendering service, but do not build static resource server for static resource proxy, static resource loading failure will occur.

4. The H5 label is incomplete

When the server performs page rendering, it will automatically complete the H5 label, such as

Two, multi-page project packaging

In vue-server-Renderer package, the relationship between client-plugin and server-plugin plug-ins and SPA page is one-to-one, that is, one SPA page corresponds to a set of client Bundle and server Bundle. That is, a client-side JSON file and a server-side JSON file correspond to a SPA application. If we create more than one SPA page in the project, the client-plugin and server-plugin plug-ins will report an error indicating multiple entries that do not match properly during packaging.

However, in many cases, we need to have multiple SPA pages in a project. For this problem, we can use shell script to call NPM command for multiple packaging using webpack, and dynamic SPA page entry matching according to command parameters in webpack. In fact, we can think of this as breaking up a multi-SPA project into multiple single spAs.

AsyncData must return a Promise object

Since the asyncData function performs data prefetch and store initialization, it is an asynchronous operation, while server-side rendering needs to return the rendered page to the browser after data prefetch is completed. So you need to set asyncData’s return value to a Promise object, and the Action object in VUex also needs to return a Promise object.

4. Server-side calls to Vue hooks

Only beforeCreate and Created hooks are triggered when Vue instance components are rendered on the server side. Therefore, the following points need to be paid attention to:

1. Place the page initialization content in beforeCreate and Created hooks as far as possible;

2. Logic that occupies global memory, such as timers, global variables, closures, etc., should not be placed in beforeCreate or Created hooks. Otherwise, it will not be deregistration in the beforeDestory method, resulting in memory leakage.

5. Server-side render template page and SPA application template use the same HTML page

Sometimes we want to use the TEMPLATE page of the SPA application directly as the template page for server-side rendering for convenience, ease of administration, and simplicity of the project. Note that the template page rendered on the server side has an extra comment flag than the SPA application template page, and the comments in the SPA application template will be removed when Webpack is packaged.

To solve this problem, you can set the WebPack configuration not to pack the SPA application template page as follows:

// vue.config.js
module.exports = {
    chainWebpack: (config) = > {
        config
            .plugin('html-index')
            .tap(options= > {
                options[0].minify = false;
                returnoptions; }); }};Copy the code

Vue-cli3 registers an HTML plug-in for each SPA page to manage the WebPack configuration. Note that when the project is a single entry, the name of the plug-in is’ HTML ‘; The plug-in name is’ HTML -${entryName} ‘, where entryName is the entry name. The plug-in name is’ HTML -${entryName} ‘.

6. The JS package shared by the client and server should support both browser and Node

When the client and server share THE JS package, the package with the “isomorphism” policy should be used, for example, axios should be used instead of vue-Resource.

review

At the beginning, we thought about some server-side rendering problems and answered them in this article. Here we review them again.

1.Vue takes Vue instance as the basic unit during page rendering. Should Vue instance also be rendered during server rendering?

The official vue-server-Renderer package provides a way to render vue instances. Renderer and BundleRenderer objects are provided to render a “single Vue instance” and a “vue instance in a VUE project” respectively. The latter approach is commonly used to dynamically match Vue instances that need to be rendered on the server side based on the routing of user requests.

2. The relationship between the user and the client is one-to-one, but the relationship between the user and the server is many-to-one. How to avoid data sharing between multiple users on the server?

Vue server – side rendering adopts client – side and server – side collaboration rendering scheme.

The client is responsible for loading static resources, using the singleton mode;

The server side is responsible for rendering the Vue instance in factory mode, which means all possible “closures” or “global variables” need to be converted to factory mode. Including but not limited to creating Vue instances, Vuex instances (Store), Module modules in Store, VUE-Router instances, and other common JS configuration files.

3. How to implement isomorphism strategy? So that the server can run the front-end code?

First of all, through WebPack, according to the different client and server environment variables, the project is packaged into browser-side recognizable mode, and Node side recognizable CommonJS2 mode;

Secondly, some common JS packages are developed by using compatible browsers and Node-based packages. For example, interface requests can be processed by axios.js.

4. How should the development environment and production environment be deployed for the Vue project rendered on the server side? What’s the difference?

Thing in common:

In either case, the server-side rendering scheme requires both client-side and server-side bundles to render together, so the project needs to be packaged twice. The client Bundle includes static files that can be recognized by the browser originally packaged by the front-end project and the client Bundle entry file. Server-side bundles package projects into commonJS2 schema and inject them into JSON files using source-Map mode.

Difference:

First, the deployment of the production environment is relatively simple and crude. The packaged client and server-side Bundle are placed on the server and run using a Node service.

However, the deployment of the development environment is more complicated because webPack is packaged and the running client is stored in memory. The solution used in this article is to read the client Bundle through HTTP requests, and use webpack packages directly in Node to package, read, and listen to the server Bundle.

5. How to ensure that the code after server-side rendering transformation can still be directly accessed by accessing static resources?

To solve this problem, one solution is to proxy all static resource requests in Node service and forward static resources back to the browser through HTTP forwarding. The other is the relatively simple and quick way used in this article, which is to set up a static resource server in the Node service and mount all static resources to a specific route.

Author: An Fengxiang

  • Didi cloud full line standard cloud server limited time special
  • New purchase cloud services 50% off in January, 40% off in March and 30% off in June
  • Registration is a gift package for beginners