Kim /vue/%E8%AE%…

The current SPA architecture trend is in full blossom, but SEO seems to be a pain point, so many popular MV * and other frameworks also put forward solutions to this pain point. Vue officially provides a quick build project tool VUE – CLI, its convenience and speed is known to all. This article is to share how to integrate SSR(Server side Render) after using vue CLI to build a project. This article mainly explains the use of two ways to achieve SSR effect.

Git addresses for two examples:
  1. vue-prerender-demo
  2. vue-ssr-demo

This article is intended for those who are familiar with VUE and have some knowledge of VUE-CLI.

My environment

  • The node v8.5.0
  • NPM v5.0.0
  • Vue – cli v2.8.2
  • MacOS 10.12.3

These are not important factors ~~

2. Method 1: Use prerender-SPa-plugin to obtain SSR effect.

2.1 illustrates

Plugin address: prerender-spa-plugin

Strictly speaking, this implementation is not called SSR, but prerender. But the effect is the same, maybe even better than SSR to some extent. Compared with complex SSR configuration, prerender configuration is simpler and faster. If there are no special requirements, you only need to add a plugin configuration to webpack. However, only h5 history routes are supported. Hash routes are not supported.

Prerender uses Phantom JS to simulate the browser environment, running the specified routing page in phantom JS so that the.vue will work in phantom and render. Prerender then takes the rendered DOM structure and writes it to the HTML file for the route. Once the service is started, enter the routing address in the real browser environment and the server returns prerender RENDERED HTML to the browser, thus achieving SSR effect.

2.2 the initialization

To ensure that vuE-CLI is successfully installed, run the following command:

Vue init webpack vue-prerender-demo // This article is configured on webpack basisCopy the code

After the press enter, the build tool will prompt some Settings for the project information. Here I chose vue-Router, code checking ESLint, and Standard, not integration testing and unit testing, because installing the package was too time-consuming.

Initialization completed:

cd vue-prerender-demo
npm install
npm run dev
Copy the code

Enter the project, install finished, start the project, ensure the normal work of the project.

2.3 configuration

To facilitate testing, I made the following changes to the initialized demo:

  1. The routingmodeModified tohistory
  2. Add a Test component, rated with the Hello component, as a separate routing page
  3. Modify therouter/index.jsAdded the configuration in/testRouting.
  4. Added to the Hello component<router-link to="/test">/test</router-link>In the Test component<router-link to="/">

Then check the page and jump between the home page ->Test-> home page. Routing works.

2.4

Then we can start our work:

1. Install prerender-spa-plugin because of dependencyphantom jsPhantom is a pain to set up and takes too long
npm install prerender-spa-plugin -D
Copy the code
2. Start prerender-related configuration:

Modify webpack.prod.conf.js to pre-render only in production.

Var PrerenderSpaPlugin = require('prerender-spa-plugin') // plugins: { //.... // configure prerender-spa-plugin new PrerenderSpaPlugin(// generate file path, Consistent with webpack packaged address path here. Join (config. Build. AssetsRoot), / / config. Build. The vue cli assetsRoot generate configuration, after packaging file address / / configuration to do pre-rendered routing, Only h5 history ['/', '/test']) //.... is supported }Copy the code
3. The compilation

Run the command:

npm run build
Copy the code

After the command completes, you can see the file structure in the dist directory:

Dist /index.html because there is a test directory that corresponds to a prerender /test route. Yes, that’s right.

Go to dist/index.html and take a look at the contents. There’s a lot more in this file than just a single

. Now the DOM structure in the body is actually the same as the DOM structure in the/path page rendered in the browser! Test /index.html is the dom structure rendered after accessing /test.

4. Verify

You can skip this step. Python is used as a quick start server.

To make sure that the end result is correct in the real world, I started an HTTP service locally using Python (without using Webpack and Node as services)

CD dist // Go to the corresponding directory python -m SimpleHTTPServer 8888 // take dist as the root directory and start port 8888Copy the code

Enter localhost:8888/test directly in your browser and right-click to view display page source code:



In the response content of /test, we can see that the dom structure after rendering is returned. The spider of the search engine can obtain the content smoothly, so as to achieve the effect of SEO.

2.5 the advantages and disadvantages

The demo address in this mode is vue-prerender-demo

3. Method 2: Use the official wheels to do THE SSR on the node

3.1 illustrates

This example only shows how to complete a relatively basic SSR, VUEX, and cache. Please refer to the official website.

Official document address: ssr.vuejs.org/zh/

The official documentation begins with “vue vs. Library version minimum requirements, why to use SSR, and compare the prerender-SPa-plugin mentioned above.”

And as follows:

This tutorial will be very in-depth and assumes that you are already familiar with vue.js itself and have decent experience with Node.js and Webpack. If you prefer a higher-level solution that provides a smooth out-of-the-box experience, you should try nuxt.js. It builds on the same Vue technology stack, but abstracts many templates and provides additional functionality, such as static site generation. However, if you need more direct control over the structure of your application, nuxt.js is not suitable for this usage scenario.

Official VUE SSR Demo: HackerNews Demo

Compared to prerender, the complexity of SSR upstart is vastly different. Follow this section to implement a basic VERSION of an SSR build configuration.

3.2 the constraint

If you are going to use SSR in Node for your VUE project, it is necessary and necessary to follow these conventions in common code:

Common code: The part that runs on both the client and server is common code.

  1. Note that the server only callsBeforeCreat and createdTwo hooks so can’t be done similar to increatedInitialize oneThe timerAnd then inMounted or destroyedDestruction of theThe timerOtherwise the server will slowly be squeezed dry by these timers
  2. Due to the single-threaded mechanism, the server-side rendering process is similar toThe singletonThe singleton operation is shared by all requests and should be usedThe factory functionTo ensure independence between each request.
  3. If you have any inBeforeCreat and createdThe hook uses a third-party API, and you need to make sure that the API is innodeThe end runs without errors, such as increatedIt is normal and perfectly reasonable to initialize a data request in a hook. But if you just use itXHRTo operate. That’s innodeEnd render when there is a problem, so should be takenaxiosThis kind ofBrowser side and server sideAll supported third-party libraries.
  4. Most important: Never use it in generic codedocumentThe API, which runs only in the browser, cannot be used only in the browsernodeThe API that the end can run.

3.3 Preparations

Initialize a project again using vue-CLI:

vue init webpack vue-ssr-demo
Copy the code

then:

cd vue-ssr-demo
npm install
npm run dev
Copy the code

Make sure the initial project works, and then start messing around

3.4 Starting to Toss

1. Install SSR support first
npm i -D vue-server-renderer
Copy the code

It is important that the VUe-server-renderer and vUE versions match consistently

2. Add routestestWith the page

A random counter was written to verify that vue’s mechanism worked properly when rendered on the server.

<template>
  <div>
    Just a test page.
    <div>
      <router-link to="/">Home</router-link>
    </div>
    <div><h2>{{mode}}</h2></div>
    <div><span>{{count}}</span></div>
    <div><button @click="count++">+1</button></div>
  </div>
</template>
<script>
  export default {
    data () {
      return {
	    mode: process.env.VUE_ENV === 'server' ? 'server' : 'client',
        count: 2
      }
    }
  }
</script>
Copy the code
In 3.srcCreate two js files under directory:
SRC ├─ entry-client.js # only run in browser ├─ entry-server.js # Only run on serverCopy the code
4. ModifyrouterConfiguration.

No matter what system routing is always the most important, server-side rendering will naturally have a common set of systems, and to avoid singleton effects, we will only export a new router instance for each request:

import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' Vue.use(Router) Export function createRouter () {return new Router({mode: 'history', routes: [{path: '/', name: 'Hello', component: HelloWorld }, { path: '/test', name: 'Test', component: () = > import (' @ / components/Test ') / / asynchronous components}}}Copy the code
5. Transformmain.js

The main.js initialization is only suitable for running in the browser, so we need to modify the file that can be used on both ends. Again, to avoid singleton effects, we will export a createApp factory function:

import Vue from 'vue' import App from './App' import { createRouter } from './router' export function createApp () { // Router = new createRouter() const app = new Vue({render: Return {App, router}}Copy the code
6. entry-client.jsAdd the following:
Import {createApp} from './main' const {app, router} = createApp() Router.onready (() => {app.$mount('#app')})Copy the code
7. entry-server.js
// entry-server.js import {createApp} from './main' export default context => {// Because there may be asynchronous routing hook functions or components, So we'll return a Promise, // so that the server can wait for everything to be ready before rendering. return new Promise((resolve, reject) => { const { app, Router.push (context.url) // Wait until the router has finished parsing possible asynchronous components and hook functions router.onReady(() => {const matchedComponents = router. GetMatchedComponents () / / can't match routing, perform reject function, and returns the if 404 (! matchedComponents.length) { // eslint-disable-next-line return reject({ code: 404})} // The Promise should resolve the application instance so that it can render resolve(app)}, reject)})}Copy the code
8. webpackconfiguration

Now that the VUE code has been handled, it’s time to modify the WebPack configuration.

The official website recommends the following configuration:

├─ ├─ webpack.class.conf # ├─ Webpack.class.conf # ├─ Webpack.class.conf # ├─ Webpack.class.conf # ├─ Webpack.class.class.conf #Copy the code

However, vue-CLI initialization also has three configuration files: base, dev, prod, we still keep these three configuration files, just need to add webpack.server.conf.js.

9. Configure the WebPack client

Change the entry configuration of webpack.base.conf.js to./ SRC /entry-client.js. This will not affect the original dev and PROd configurations.

The server configuration also references the Base configuration, but overwrites the entry into server-entry.js by merging.

Generate the client manifest

Benefits:

  1. Can be replaced if the generated file name has a hashhtml-webpack-pluginTo inject the correct resource URL.
  2. When rendering bundles through WebPack’s on-demand code splitting feature, we can ensure optimized resource preloading/data prefetching of chunks and intelligent injection of the required asynchronous chunks into<script>To avoid waterfall requests from clients, and to improve TTI – time-to-interactive time.

It is easy to add a plugin to the prod configuration and add it to the plugin:

const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') // ... / /... plugins: [ new webpack.DefinePlugin({ 'process.env': env, 'process.env.VUE_ENV': '"client"' // add process.env.vue_env}), //... // Also need to remove the HtmlWebpackPlugin for prod, because we have vue-ssr-client-manifest.json, // new HtmlWebpackPlugin({// filename: config.build.index, // template: 'index.html', // inject: true, // minify: { // removeComments: true, // collapseWhitespace: true, // removeAttributeQuotes: true // // more options: // // https://github.com/kangax/html-minifier#options-quick-reference // }, // // necessary to consistently work with multiple chunks via CommonsChunkPlugin // chunksSortMode: 'dependency' //}), // this plugin generates' vue-ssr-client-manifest.json 'in the output directory. New VueSSRClientPlugin()] //...Copy the code
10. Configure the WebPack server

The server configuration is useful to run with a new plug-in: NPM i-d webpack-node-externals

Webpack.server.conf.js is configured as follows:

const webpack = require('webpack') const merge = require('webpack-merge') const nodeExternals = require('webpack-node-externals') const baseConfig = require('./webpack.base.conf.js') const VueSSRServerPlugin = BaseConfig. Module. rules[1]. Options = "module.exports =" Merge (baseConfig, {// Point entry to the application's server Entry file entry: './ SRC /entry-server.js', // This allows Webpack to handle dynamic imports in a Node-appropriate fashion, // and also allows Vue component compilation, // Tell the VUe-loader to transport server-oriented code. Target: 'node', // provide source map support for bundle renderer 'source-map', // tell server bundle to use Node-style exports output: {libraryTarget: 'commonjs2' }, // https://webpack.js.org/configuration/externals/#function // https://github.com/liady/webpack-node-externals // Externalizing applications depends on modules. Make server builds faster, // and generate smaller bundles. Externals: nodeExternals({// Do not externalize dependencies that Webpack needs to process. // You can add more file types here. // You should also whitelist dependencies that modify 'global' (e.g. polyfill) whitelist: /\.css$/}), plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 'process.env.VUE_ENV': JSON 'new VueSSRServerPlugin()]})Copy the code

Notice that an attribute is removed from baseConfig

Baseconfig.module.rules [1].options = "// Remove separate CSS package plug-insCopy the code
Configuration of 11.package.jsonAdded the package server build command and modified the original package command
"scripts": {
	//...
	"build:client": "node build/build.js",
    "build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.conf.js --progress --hide-modules",
    "build": "rimraf dist && npm run build:client && npm run build:server"
}
Copy the code

If cross-env is not found, install NPM i-d cross-env

12. Modifyindex.html
<! DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue-ssr-demo</title> </head> <body> <! --vue-ssr-outlet--> </body> </html>Copy the code

Delete the original

and leave only one tag in the body :
. The server will automatically generate a

, and the client will mount it via app.$mount(‘#app’), rendered responsive.

The dev mode in the project also applies to this template. The dev mode in the project also applies to this template.

  1. The simplest way is todevThe pattern creates a separate HTML template…
  2. fordevThe pattern also integrates server-side rendering mode, so it makes sense to have both production and development environments in server-side rendering mode. (The official example works like this.)
13. Run the build command
npm run build
Copy the code

Then you can see two generated JSON files in the dist directory: vue-ssR-server-bundle. json and vue-SSR-client-manifest.json.

Both files are applied to the Node side for server-side rendering and injection of static resource files.

14. Build the server side (used by the official exampleexpress, so this demo will usekoa2Of course, it doesn’t matter whether koA or Express…)
npm i -S koa
Copy the code

Create server.js in the project root directory as follows

const Koa = require('koa')
const app = new Koa()

// response
app.use(ctx => {
  ctx.body = 'Hello Koa'
})

app.listen(3001)
Copy the code

Run Node server.js, access localhost:3001, and make sure your browser gets Hello Koa.

Write server-side code

The KOA static resource middleware needs to be installed: NPM I-d KOA-static

The server.js code looks like this:

const Koa = require('koa') const app = new Koa() const fs = require('fs') const path = require('path') const { createBundleRenderer } = require('vue-server-renderer') const resolve = file => path.resolve(__dirname, Const renderer = createBundleRenderer(require('./dist/vue- SSR -server-bundle.json'), {// runInNewContext: false, // template HTML file template: fs.readFileSync(resolve('./index.html'), 'utf-8'), // client manifest clientManifest: require('./dist/vue-ssr-client-manifest.json') }) function renderToString (context) { return new Promise((resolve, reject) => { renderer.renderToString(context, (err, html) => { err ? reject(err) : resolve(html) }) }) } app.use(require('koa-static')(resolve('./dist'))) // response app.use(async (ctx, Next) => {try {const context = {title: 'server render test ', // {{title}} url: Body = await renderToString(context) // Set the request header ctx.set(' content-type ', 'text/ HTML ') ctx.set('Server', 'Koa2 Server side render')} catch (e) { Next ()}}) app.listen(3001)Copy the code

Run the start service command:

node server.js
Copy the code
16. You’re done

Access the browser: localhost:3001/test. The screenshot shows the page successfully rendered by the server

The data attribute in test.vue proves that server-side rendering is working properly (mode: process.env.vue_env === ‘server’? ‘server’ : ‘client’,), but mode is equal to client when the client data is mixed.

3.4 the advantages and disadvantages

This example git address: vue-SSR-demo

Above, to the restless heart…