preface

Most Vue projects should support SSR for SEO purposes, as search engines are a big traffic entry point for WEB applications. Vue SSR is now mature, but the cost of transforming a SPA application into A SSR application is still a bit high, and the workload is similar to refactoring the front end. In addition, the technical requirements for the front end are also quite high, requiring familiarity with Vue and application experience of Node.js and Webpack.

The introduction of

Vue is a framework for building client-side applications, where Vue components are rendered in the browser. Server-side rendering refers to rendering vUE components as assembled HTML strings on the server side and then sending them directly to the browser. Finally, these static tags need to be “activated” into a fully interactive application on the client side.

Benefits of server-side rendering

  1. For better SEO, search engine crawlers can crawl well rendered pages

  2. Faster content arrival time (faster first screen loading), because the server only needs to return the rendered HTML, this part of the code is small, so the user experience is better

Disadvantages of server-side rendering

  1. First of all, the development cost is relatively high, such as some declaration cycle hook functions (such as beforeCreate, created) can run in the server and client, so the third party library to do special processing, in order to run in the server rendering application.

  2. Since Nodejs is the middle tier for server-side rendering, you need to deploy your project in the Node.js Server runtime environment. In high traffic environments, server load and caching policies are also required

The principle of analytic

First attached demo address: github.com/wmui/vue-ss…

Step 1: Write entry-client.js and entry-server.js

Entry-client. js is executed only in the browser environment, so you need to call the $mount method to mount the DOM node

import Vue from 'vue';
import App from './App.vue';
import createStore from './store/index.js';

function createApp() {
  const store = createStore();
  const app = new Vue({
      store,
      render: h= > h(App)
  });
  return {app, store}
}

const { app, store } = createApp();

// Replace the entire state with the data in window.__initial_state__, so that the client can freely manipulate the data in the state after the server has finished rendering
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__);
}

app.$mount('#app');

Copy the code

Entry-server. js needs to export a function that will be called during server rendering

import Vue from 'vue';
import App from './App.vue';
import createStore from './store/index.js';

export default function(context) {
  // Context is the context object
  const store = createStore();
  let app = new Vue({
    store,
    render: h= > h(App)
  });

  // Find all asyncData methods
  let components = App.components;
  let asyncDataArr = []; Collection / / promise
  for (let key in components) {
    if(! components.hasOwnProperty(key))continue;
    let component = components[key];
    if (component.asyncData) {
      asyncDataArr.push(component.asyncData({store})) // Pass the store to asyncData}}// All requests are executed in parallel
  return Promise.all(asyncDataArr).then((a)= > {
    Window.__initial_state__ = window.__initial_state__ = window.__initial_state__
    // Now you know where the window.__initial_state__ in entry-client.js comes from. It is added to the context during server rendering
    context.state = store.state;
    return app;
  });
};
Copy the code

What does asyncData do? In fact, this function is used specifically to request data, you may ask why not request data in beforeCreate or created, but also to define a special function? Although beforeCreate and Created are executed on the server (other periodic functions are only executed on the client), we all know that the request is asynchronous, which causes the request to be sent before the data is returned, rendering will be finished, so the Ajax returned data can not be rendered. So you need to find a way to wait until all the data is returned before rendering the component

AsyncData needs to return a promise so that it can wait until all requests are complete before rendering the component. Here is an example of using asyncData in the foo group value, where the data is requested

export default {
  asyncData: function({store}) {
    return store.dispatch('GET_ARTICLE') / / return promise
  },
  computed: {
    article() {
      return this.$store.state.article
    }
  }
}
Copy the code

Step 2: Configure WebPack

Webpack configuration is relatively simple, but needs to be configured separately for the client and server

Webpack.client.conf.js is obviously used to package client applications

module.exports = merge(base, {
  entry: {
    client: path.join(__dirname, '.. /entry-client.js')}});Copy the code

Webpack.server.conf. js is used to package the server application, where you need to specify the Node environment

module.exports = merge(base, {
  target: 'node'.// Specify the node environment
  entry: {
    server: path.join(__dirname, '.. /entry-server.js')},output: {
    filename: '[name].js'.// server.js
    libraryTarget: 'commonjs2' // Must be packaged according to the CommonJS specification to be called by the server.
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, '.. /index.ssr.html'),
      filename: 'index.ssr.html'.files: {
        js: 'client.js'
      }, // Client.js needs to be introduced in HTML
      excludeChunks: ['server'] // Server.js is executed only on the server side, so it cannot be packaged into HTML]}}));Copy the code

Step 3: Start the service

In start.js we need to load server.js and return the rendered HTML to the browser using the renderToString method

const bundle = fs.readFileSync(path.resolve(__dirname, 'dist/server.js'), 'utf-8');
const renderer = require('vue-server-renderer').createBundleRenderer(bundle, {
  template: fs.readFileSync(path.resolve(__dirname, 'dist/index.ssr.html'), 'utf-8') // The server renders the data
});


server.get(The '*', (req, res) => {
  renderer.renderToString((err, html) = > {
    // console.log(html)
    if (err) {
      console.error(err);
      res.status(500).end('Server internal error');
      return; } res.end(html); })});Copy the code

rendering

Demo has been uploaded to Github: github.com/wmui/vue-ss…

conclusion

I have been practicing Vue SSR for a period of time, and I find it challenging to build a complete SSR service framework. Maybe Nuxt is a good choice. Those who are interested in Nuxt can refer to my small open source Essay

Above, thanks for reading!