directory

  • What are server-side rendering technologies and their application scenarios
  • What is pre-rendering, server rendering VS pre-rendering
  • Principles of server-side rendering implementation
  • Set up a server-side rendering project
  • Server side rendering performance optimization

What is Vue Server Rendering (SSR)

Vue server rendering involves rendering Vue instances on the server as HTML strings, sending them directly to the browser, and finally “blending” static tags into a fully interactive application on the client.

Why use Server-side Rendering (SSR)

  • Better SEO, thanks to search engine crawler crawler tools can view fully rendered pages directly.
  • Faster first screen rendering speed. Especially for devices with slow network speed or running speed, there is no need to wait for all JS to download and parse before rendering the page, but to render the page on the server and send it directly to the client.

Server Rendering (SSR) VS Prerendering

  • Similarities: are to solve the problem of single page SEO, faster content arrival time.
  • Differences: 1. Different implementation principles and solutions: SSR relies on node.js server to build static resources, while PRERender relies on WebPack integration as prerende-SPa-plugin to extract static resources and show them to the front end. Server rendering allows real-time compilation on the server side. Prerender simply generates static HTML files for specific routes at build time for SEO purposes. The advantage of prerender is simpler configuration and the ability to use the front end as a completely static site.

Prerender webPack configuration

const path = require('path')
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  plugins: [
    ...
    new PrerenderSPAPlugin({
      // Required - The path to the webpack-outputted app to prerender.
      staticDir: path.join(__dirname, 'dist'),
      // Required - Routes to render.
      routes: [ '/'.'/about'.'/some/deep/nested/route'],})]}Copy the code

Server rendering schematic

SSR implementation technology stack

Server: Nodejs Front-end framework Vue2.0+ Front-end building tools: Webpack Code inspection: ESLint source code: ES6 Front-end routing: VUE-Router State management: VUex server communication: AXIOS Log management: Log4JS Project automation deployment tools: jenkins

The server renders a Vue instance

// create a Vue instance const Vue = require('vue') const app = new Vue({template: '<div>Hello World</div>'})'vue-server-renderer'Renderer. RenderToString (app, (err, HTML) => {if (err) throw err
  console.log(html)
  // => <div data-server-rendered="true">Hello World</div>
})
Copy the code

Node service integrated with Express

const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get(The '*', (req, res) => { const app = new Vue({ data: { url: req.url }, template: {{URL}}</div> '}) renderer.renderToString(app, (err, HTML) => {if (err) {
      res.status(500).end('Internal Server Error')
      return} res.end(` <! DOCTYPE html> <html lang="en">
        <head><title>Hello</title></head>
        <body>${html}</body>
      </html>
    `)
  })
})
server.listen(8080)
Copy the code

The server renders the project directory structure

App.js code structure

import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import { sync } from 'vuex-router-sync'
import Element from 'element-ui'Vue.use(Element) // sync the router with the vuex store. // this registers `store.state.route` sync(store, Router) /** * Create vue instance * here to inject router Store to all sub-components * so that 'this' can be used anywhere.$router` and `this.$store`
 * @type {Vue$2} */ const app = new Vue({router, store, render: h => h(app)}) /** * This is different from browser rendering */export { app, router, store }
Copy the code

Entry-client.js code structure

import 'es6-promise/auto'
import { app, store, router } from './app'

// prime the store with server-initialized state.
// the state is determined during SSR and inlined in the page markup.
if(window.__initial_state__) {store.replacestate (window.__initial_state__)} /** * Asynchronous components */ router.onReady(() => {// Start mounting your APP to the DOM.$mount('#app')
})

// service worker
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')}Copy the code

Server-entry. js code structure

import { app, router, store } from './app'const isDev = process.env.NODE_ENV ! = ='production'

// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
  const s = isDev && Date.now()

  return new Promise((resolve, reject) => {
    // set router's location router.push(context.url) // wait until router has resolved possible async hooks router.onReady(() => { const  matchedComponents = router.getMatchedComponents() // no matched routes if (! matchedComponents.length) { reject({ code: 404 }) } // Call preFetch hooks on components matched by the route. // A preFetch hook dispatches a store action and returns a Promise, // which is resolved when the action is complete and store state has been // updated. Promise.all(matchedComponents.map(component => { return component.preFetch && component.preFetch(store) })).then(() => {  isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`) // After all preFetch hooks are resolved, our store is now // filled with the state needed to render the app. // Expose the state on the render context, and let the request handler // inline the state in the HTML response. This allows the client-side // store to pick-up the server-side state without having to duplicate // the initial data fetching on the client. context.state = store.state resolve(app) }).catch(reject) }) }) }Copy the code

General code considerations

Our generic code is a set of code that runs separately in the browser and Node. js environment, so there are a few things to be aware of when writing code: 1. Only the beforeCreate and created lifecycle functions will be called during server rendering. The rest of the life cycle can only be invoked lazily in the browser environment, and Node.js ignores these life cycle functions. 2. Generic code does not accept platform-specific apis (such as Document and Window), use cross-platform AXIos (which exposes the same API to browsers and Node.js) to send requests to browsers and Node.js environments. 3. Most custom instructions manipulate the DOM directly and therefore cause errors during server-side rendering (SSR)

Routing and code splitting

1. Introduce vue-Router to make single page application 2. Application code splitting or lazy loading can help reduce the volume of resources downloaded by the browser in the initial rendering, which can greatly improve the TTI time-to-interactive time of large bundles. The key here is, for the initial first screen, “load only what you need”.

// Change it here... import Foo from'./Foo.vue'// Const Foo = () => import()'./Foo.vue')
Copy the code

Data prefetch and status

During server-side rendering (SSR), we are essentially rendering a “snapshot” of our application, so if the application relies on some asynchronous data, it needs to be prefetched and parsed before starting the rendering process.

Another concern is that on the client side, you need to get exactly the same data as the server-side application before mounting it to the client-side application – otherwise, the client application will use a different state than the server-side application and then cause the mixing to fail.

To solve this problem, the retrieved data needs to be located outside the view component, that is, in a dedicated data store or state container. First, on the server side, we can prefetch data and populate the store before rendering. In addition, we will serialize and inline the state in HTML. This allows you to get the inline state directly from store before mounting the client application.

To do this, we will use the official state management library Vuex.

Client-side mixing

Client-side activation refers to the process by which the Vue takes over static HTML sent by the server at the browser side and turns it into a dynamic DOM managed by the Vue. If you examine the server rendered output, you’ll notice that the root element of the application has a special attribute:

<div id="app" data-server-rendered="true">
Copy the code

Data-server-rendered special attributes let the client Vue know that the HTML portion was rendered by the Vue on the server and should be mounted in active mode.

In development mode, Vue will infer whether the virtual DOM tree generated by the client matches the DOM structure rendered from the server. If there is no match, it will exit the blending mode, discard the existing DOM and start rendering from scratch. In production mode, this detection is skipped to avoid performance degradation.

Client build configuration

The client config and base config are roughly the same. Obviously you need to point entry to your client entry file. In addition, if you use CommonsChunkPlugin, be sure to use it only in client config, since the server package requires a separate chunk of entry

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
module.exports = merge(baseConfig, {
  entry: '/path/to/entry-client.js', plugins: [// Important information: This separates the WebPack runtime into a boot chunk, / / so that you can in the right after the injection of asynchronous chunk. / / it is also for your application/vendor code provides a better cache. The new webpack.optimize.Com monsChunkPlugin ({name:"manifest"MinChunks: Infinity}), // This plugin generates' vue-ssR-client-manifest.json 'in the output directory. new VueSSRClientPlugin() ] })Copy the code

Server build configuration

The server configuration is used to generate the Server bundle passed to createBundleRenderer. It should look something like this:

const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin'Module. Exports = merge(baseConfig, {// Set entry to the server entry file of the application:'/path/to/entry-server.js', // This allows Webpack to handle dynamic imports in a Node-appropriate fashion, // and also allows the Vue component to compile, // Tell the VUe-loader to transport server-oriented code. target:'node'// The bundle renderer is providedsourceThe map support devtool:'source-map'// Server bundle exports (node-style exports) output: {libraryTarget:'commonjs2'
  },
  // https://webpack.js.org/configuration/externals/#function/ / https://github.com/liady/webpack-node-externals / / external applications depend on the module. 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. For example, the *. Vue raw file is not processed, // You should also whitelist dependent modules that modify 'global' (e.g. polyfill) : /\.css$/}), // this is a plug-in that builds the entire output of the server // into a single JSON file. Json 'plugins: [new VueSSRServerPlugin()]})Copy the code

The cache

In most cases, server-rendered applications rely on external data, so page content is inherently dynamic and cannot be cached for long periods of time. However, if the content is not user-specific (that is, always render the same content for all users for the same URL), a caching strategy called micro-caching can be used to significantly improve the ability of an application to handle high traffic.

Const microCache = LRU({Max: 100, maxAge: 1000) }) const isCacheable = req => {// The implementation logic is to check whether the request is user-specific. // Only non-user-specific pages will be cached} server.get(The '*', (req, res) => {
  const cacheable = isCacheable(req)
  if (cacheable) {
    const hit = microCache.get(req.url)
    if (hit) {
      return res.end(hit)
    }
  }
  renderer.renderToString((err, html) => {
    res.end(html)
    if (cacheable) {
      microCache.set(req.url, html)
    }
  })
})
Copy the code

Streaming rendering

Both the base and bundle renderers for vue-server-Renderer provide streaming rendering out of the box. All you need to do is replace renderToString with renderToStream:

const stream = renderer.renderToStream(context)
Copy the code

The value returned is Node.js stream:

let html = ' '
stream.on('data', data => {
  html += data.toString()
})
stream.on('end', () => {console.log(HTML) // Render complete}) stream.on()'error', err => {
  // handle error...
})
Copy the code

Nuxt.js

Building a server-side rendered application from scratch is quite complex. Nuxt is a higher-level framework based on the Vue ecosystem that provides an extremely convenient development experience for developing server-side rendered Vue applications