Rendering Practice of Server Side Based on Phantomjs+Node+Express+Vuejs1.x

  express, node.js, phantomjs, vue.js


Project address: …

With the release of Vue 2.0, server-side rendering became its hot selling point. Before that, the problems of the first screen loading time and SEO of single-page applications have puzzled developers all the time, and also restricted the use scenario of the front-end framework to a certain extent. The server-side rendering scheme proposed by React has better solved the above two pain points and has won the favor of developers. However, it also adds another reason to criticize Vue-VUE has no server-side rendering. In order to solve this problem, Vue’s community has also contributed a program calledVueServer. However, this product is not a simple server rendering scheme, but is equivalent to another Vue of a server. Just look at its readme to see:

VueServer.js is designed for static HTML rendering. It has no real reactivity.
Also, the module is not running original Vue.js on server. It has its own implementation.
It means VueServer.js is just trying to perfectly reproduce the same result as Vue.js does.

So is there a general solution that allows us to use the native Vue 1.x and render the server happily? Now please listen to me carefully.

Server Rendering (SSR)

Before starting the article, it is necessary for us to know what server-side rendering is and why server-side rendering is needed (students who know it can skip it).
Sever Side Render (SSR) sounds great, but the principle is our most common “server directly spit out the page.” We know that traditional websites combine data with html and send them to the client through splicing and template filling. This process of combining data with html is server-side rendering.

The first benefit of server-side rendering is the loading time of the first screen. Because the html sent from the back end is complete html with data, the browser can use it directly. On the contrary, taking a single-page application developed by Vue 1.x as an example, the html sent by the server is just an empty template. the browser asynchronously requests data from the back end according to js and renders it into html. Js of a large single-page application is often very large, and the number of asynchronous requests is also very large. The direct result is that the first screen takes a long time to load. Under the condition of poor network speed, the long waiting process of white screen or loading is really unfriendly to the user experience.

On the other hand, the general search engine crawler cannot execute js code in html (except for my big Google), so for single-page applications, the crawler only obtains empty html, so websites that need SEO rarely adopt single-page application schemes. We can look at examples-

First, let’s write an html file that generates content through js:

<!  --  SPA.html -->
 <!  DOCTYPE html>
 <html lang="en">
 <meta charset="UTF-8">
 var div = document.createElement('div')
 div.innerHTML = 'Hello World!'

The browser opens and outputs “Hello World!” Good, no problem.

Next, let’s write a little reptile:

'use strict'
 const superagent = require('superagent')
 const cheerio = require('cheerio')
 var theUrl = 'http://localhost:3000/spa.html'
 const spider = (link) => {
 let promise =  new Promise( (resolve, reject) => {
 .end((err, res) => {
 if (err) return console.log(err)
 let $ = cheerio.load(res.text)
 return promise

Run, output results are as follows:


As you can see, in<body></body>No corresponding is generated within the tagdivThe crawler cannot parse the js code in the page.


In order to achieve server-side rendering, our protagonistPhantomJSHere we go.

PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

In a nutshell, Phantomjs encapsulates a webkit kernel, so it can be used to parse JS code. In addition, it also has other very practical uses. The specific usage method can be viewed on its website. Because PhantomJS is a binary file and needs to be installed and used, it is rather troublesome, so I found another NodeJS module that encapsulates PhantomJS-phantomjs-node

PhantomJS integration module for NodeJS

With it, you can happily use PhantomJS with node!

npm install phantom --save

Create a new onephantom-demo.jsFile, write the following:

var phantom = require('phantom');
 var sitepage = null;
 var phInstance = null;
 .then(instance => {
 phInstance = instance;
 return instance.createPage();
 .then(page => {
 sitepage = page;
 .then(status => {
 .then(content => {
 .catch(error => {

You will see the whole in the console.http://localhost:3000/spa.htmlThe content of<div>Hello World! </div>


The Vue 1.x project is rendered on the server side with Express.

Next, the actual combat began. First of all, we will set up a Vue 1.x project for use here.vue-cliBuild:

npm install vue-cli -g
 vue init webpack vue-ssr

Execute the following code in the generated project:

npm install
 npm run build

You can see that a\distDirectory, which contains the completed Vue 1.x projects:

|__  index.html
 |__ static
 |__ css
 |__ app.b5a0280c4465a06f7978ec4d12a0e364.css
 |__ js
 |__ app.efe50318ee82ab81606b.js
 |__ manifest.e2e455c7f6523a9f4859.js
 |__ vendor.13a0cfff63c57c979bbc.js

Next, let’s just find a place to build the Express project:

express Node-SSR -e
 cd Node-SSR && npm install
 npm install phantom --save

Then, we put the previous\distUnder the directory\static\cssAnd\static\jsAll the codes in the are copied and pasted to the of the newly generated Express project respectively\public\stylesheetsAnd\public\javascriptsIn the folder (note, be sure to include all*.mapFile), at the same time\distUnder the directoryindex.htmlRename tovue-index.ejs, placed in the of the Express project\viewIn the folder, rewrite it and change all reference paths to/stylesheets/Or/javascripts/The beginning.

Next, let’s open the\routes\index.jsFile, rewritten as follows:

const express = require('express')
 const router = express.Router()
 const phantom = require('phantom')
 /* GET home page. */
 router.get('/render-vue', (req, res, next) => {
 router.get('/vue', (req, res, next) => {
 let sitepage = null
 let phInstance = null
 let response = res
 .then(instance => {
 phInstance = instance
 return instance.createPage()
 .then(page => {
 sitepage = page
 .then(status => {
 console.log('status is: ' + status)
 .then(content => {
 // console.log(content)
 .catch(error => {
 module.exports = router

Now we use the former reptile to crawlhttp://localhost:3000/render-vueThe results are as follows:


You can see some js that have not been executed.

Then we climb to get ithttp://localhost:3000/vue, see what the result is:


Full content.

We can also open the above two addresses in the browser, although the results are shown in the following figure, but through the developer toolNetworkOption, you can see that the requested html content is different.


At this point, the server-side rendering practice based on PhantomJS+Node+Express+VueJS 1.x has come to an end.


Since Phantomjs also takes some time to open the page and parse the JS code in it, we should not re-execute the server rendering every time the user requests, but should let the server cache the results of PhantomJS rendering, so that the user only needs to return the cached results for each request, greatly reducing the server pressure and saving time.


This article is only for the purpose of learning, and has not carried out in-depth research. At the same time, the method studied in this article is not only applicable to Vue projects, but theoretically any single-page application project after construction can be used. If readers find any mistakes or omissions in the article, please give them some advice. I am very grateful. If there is a better rendering method for the server, please share it with me.

Thank you for reading. I am Jrain, welcome to pay attention.My column, will not regularly share their learning experience, development experience, carrying dry goods outside the wall. See you next time!