1. What is SSR (server-side rendering)?
Traditional vue project browser rendering mode
Disadvantages: 1. SEO problems
2. First screen speed problem
3. Performance issues
ssr server rendering mode
advantage:
1. Better SEO, because the search engine crawler can directly view the fully rendered page
2. Fast first screen rendering
SSR is simply to display the page directly on the client after rendering on the server.
2. SSR principle
Simple example
index.template.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{title}}</title> {{{ metas }}} </head> <body> <!--vue-ssr-outlet--> </body> </html>
server.js
// eslint-disable-next-line @typescript-eslint/no-var-requires const Vue = require('vue'); // eslint-disable-next-line @typescript-eslint/no-var-requires const server = require('express')(); // eslint-disable-next-line @typescript-eslint/no-var-requires const template = require('fs').readFileSync('./index.template.html', 'utf-8'); // eslint-disable-next-line @typescript-eslint/no-var-requires const renderer = require('vue-server-renderer').createRenderer({ template, }); const context = { title: 'vue ssr', metas: ` <meta name="keyword" content="vue,ssr"> <meta name="description" content="vue srr demo"> `, }; server.get('*', (req, res) => { const app = new Vue({ data: { url: req.url }, template: `<div>Visited URL Yes: {{ url }}</div>`, }); renderer .renderToString(app, context, (err, html) => { console.log(html); console.log(err) if (err) { res.status(500).end('Internal Server Error') return; } res.end(html); }); }) server.listen(8081);
Build steps
vue projects are mounted to html through virtual DOM, so for spa projects, crawlers only see the initial structure. Virtual DOM should be finally transformed into real DOM through certain methods. Virtual DOM is JS object. The rendering process of the whole server is completed by compiling virtual DOM into complete html.
After we parse the virtual DOM into html through server-side rendering, you will find that the events on the page cannot be triggered. That's because the server-side rendering Vue server renderer plug-in does not do this, so we need the client-side rendering again, referred to as isomorphism. Therefore, Vue server rendering is actually rendered twice. An official figure is given below:
Two bundle files need to be generated through Webpack packaging:
Client Bundle for browser. It is similar to the pure Vue front-end project Bundle
Server Bundle, used by server-side SSR, is a json file
Whether or not the project was built using Vue cli, regardless of what it was like before. There will be this construction and transformation process. The Vue server renderer library will be used in the construction and transformation. Note that the Vue server renderer version should be the same as the Vue version.
Directory structure after packaging
3. Reconstruction SSR of Vue project (vue-cli4 / vue2 as an example)
1. Install dependent packages
npm install vue-server-renderer lodash.merge webpack-node-externals cross-env
2. Modify router
// Original writing // const router = new VueRouter({ // mode: 'history', // base: process.env.BASE_URL, // routes // }) // // export default router // Revised writing export default function createRouter() { return new VueRouter({ mode: "history", // Be sure to history base: process.env.BASE_URL, routes, }); }
3. Modify main ts
// Original writing // new Vue({ // router, // store, // render: h => h(App) // }).$mount('#app') // Revised writing export function createApp() { // Create router const router = createRouter(); const app = new Vue({ router, render: (h) => h(App), }); return { app, router }; }
4. Create entry client js
import { createApp } from './main' const { app } = createApp() app.$mount('#app')
5. Create entry server js
import {createApp} from "./main.ts"; // context is actually server / index JS, and server / index will be mentioned later js // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export default context => { return new Promise((resolve, reject) => { const {app, router} = createApp(); router.push(context.url) ; router.onReady(()=>{ // Does it match the components we want to use const matchs = router.getMatchedComponents(); if(!matchs) { return reject({code: 404}) } resolve(app); }, reject); }) }
6. Modify the webpack package file
vue.config.js
// Server rendering plug-in const VueSSRServerPlugin = require("vue-server-renderer/server-plugin"); // Generate service end package const VueSSRClientPlugin = require("vue-server-renderer/client-plugin"); // Generate client package const nodeExternals = require("webpack-node-externals"); const merge = require("lodash.merge"); // Environment variable: determines whether the portal is a client or a server. WEBPACK_TARGET is set in the startup item. See package JSON file const TARGET_NODE = process.env.WEBPACK_TARGET === "node"; const target = TARGET_NODE ? "server" : "client"; module.exports = { css: { extract: false }, outputDir: "./dist/" + target, configureWebpack: () => ({ // Point the entry to the server / client file of the application entry: `./src/entry-${target}.js`, // Provide source map support for bundle renderer devtool: "source-map", // This allows webpack to handle dynamic import in a Node appropriate manner / / and also tells' Vue loader 'to transport server oriented code when compiling Vue components. target: TARGET_NODE ? "node" : "web", node: TARGET_NODE ? undefined : false, output: { // Here, configure the server side to build in the style of node libraryTarget: TARGET_NODE ? "commonjs2" : undefined }, // External application dependent modules. You can make the server build faster and generate smaller bundle files. externals: TARGET_NODE ? nodeExternals({ // Do not externalize the dependent modules that webpack needs to handle. (formerly whitelist, changed to allowlist) allowlist: [/\.css$/] }) : undefined, optimization: { splitChunks: TARGET_NODE ? false : undefined}, // This is a plug-in that builds the entire output of the server into a single JSON file// The default server file name is ` Vue SSR server bundle JSON ` / / the default file name of the client is ` Vue SSR client manifest json` plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()] }), chainWebpack: config => { config.module .rule("vue") .use("vue-loader") .tap(options => { merge(options, { optimizeSSR: false }); }); } };
7. Create SSR html template
index.template.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>{{title}}test ssr</title> </head> <body> <!--vue-ssr-outlet--> </body> </html>
8. nodejs server
// nodejs server const express = require("express"); const fs = require("fs"); // Create express instance and vue instance const app = express(); // Create a renderer to get a createbundlerender const { createBundleRenderer } = require("vue-server-renderer"); const serverBundle = require("../dist/server/vue-ssr-server-bundle.json"); const clientManifest = require("../dist/client/vue-ssr-client-manifest.json"); const template = fs.readFileSync("../src/index.template.html", "utf-8"); // ssr template file const renderer = createBundleRenderer(serverBundle, { runInNewContext: false, template, clientManifest, }); // The middleware handles static file requests app.use(express.static("../dist/client", { index: false })); // false prevents it from rendering as dist / client / index html // app.use(express.static('../dist/client') / / if you change to this line of code, you need to put dist / client / index Html is deleted, otherwise the index.html in this directory will be rendered first HTML file // Front end request return data app.get("*", async (req, res) => { try { const context = { url: req.url, title: "ssr",}; // nodejs stream data. The file is too large. Using renderToString will cause a card const stream = renderer.renderToStream(context); let buffer = []; stream.on("data", (chunk) => { buffer.push(chunk); }); stream.on("end", () => { res.end(Buffer.concat(buffer)); }); } catch (error) { console.log(error); res.status(500).send("Server internal error"); } }); /*Service startup*/ const port = 8091; app.listen(port, () => { console.log(`server started at localhost:${port}`); });
9. Modify package json
"build:client": "vue-cli-service build", "build:server": "cross-env WEBPACK_TARGET=node vue-cli-service build --mode server", "build": "npm run build:server && npm run build:client", "service": "cd server && node index.js"
10. Start service
Packaged as client and server
npm run build
Start node service
npm run service
GitHub address: https://github.com/wang12321/SSR