SSR (Server-Side Rendering) gets all the love these days. Speeding up initial-page-loads by sending a full HTML page instead of a skeleton with a few scripts is a really great idea. There’s a catch though. SSR is hard. There are a lot of things to worry about and it breaks easily. The truth is, in most cases, SSR is overkill. You can gain most of the benefits of SSR in your Vue.js app by using prerender-spa-plugin to pre-render your site to static pages at build time.
Pre-rendering is sort of like Server-Side Rendering mixed with static site generation, but simpler. You tell the pre-renderer which routes you want, then it fires up a browser or equivalent environment, loads the pages, and dumps the resulting HTML into file paths that match the routes. This gives you fully-rendered static pages, resulting in faster perceived load times and no need for odd hacks for deployment on static site servers. Once the JavaScript finishes loading, your app will continue to work as normal.
Pre-rendering isn’t a silver bullet though, don’t use it if you have hundreds of routes, or need to pre-render dynamic content without placeholders.
Anyway, enough chit-chat. Let’s see how to do it.
Note: prerender-spa-plugin
3.0 is still in alpha, so API usage may change in the future. I’ll try to keep this article up-to-date. See the official documentation if something here doesn’t work properly.
This guide assumes you’ve set up a quick projet with vue-cli with the webpack-simple template, though it’s pretty much the same for any webpack setup.
Install prerender-spa-plugin in your Vue.js project.
# Yarn
$ yarn add prerender-spa-plugin@next -D
# or NPM
$ npm install prerender-spa-plugin@next --save-dev
Then, in webpack.config.js
, require()
the relevant package.
var path = require('path')
var webpack = require('webpack')
// Add these
const PrerenderSPAPlugin = require('prerender-spa-plugin')
// Renders headlessly in a downloaded version of Chromium through puppeteer
const PuppeteerRenderer = PrerenderSPAPlugin.PuppeteerRenderer
...
module.exports = {
...
plugins: [
new PrerenderSPAPlugin({
staticDir: __dirname, // The path to the folder where index.html is.
routes: ['/'], // List of routes to prerender.
renderer: new PuppeteerRenderer()
})
]
}
...
Now, after you run webpack again, you should find that index.html
in the project root now contains the rendered content of the page as well.
PuppeteerRenderer is great… except when it isn’t. It’s not super fast and can’t render a ton of pages without hogging a ton of system resources.
If you need to render hundreds or thousands of pages, JSDOMRenderer might be a better choice. jsdom fakes a browser environment inside of Node.js and mocks as much as it can. It does a fairly good job, but can’t handle everything. You may need to adjust your app to work around certain new or unusual APIs that might not be present.
JSDOMRenderer doesn’t come with prerender-spa-plugin
by default. You’ll have to install it with.
# Yarn
$ yarn add @prerenderer/renderer-jsdom -D
# NPM
$ npm install @prerenderer/renderer-jsdom --save-dev
JSDOMRenderer Example:
var path = require('path')
var webpack = require('webpack')
// Add these
const PrerenderSPAPlugin = require('prerender-spa-plugin')
const JSDOMRenderer = require('@prerenderer/renderer-jsdom')
...
module.exports = {
...
plugins: [
new PrerenderSPAPlugin({
staticDir: __dirname, // The path to the folder where index.html is.
routes: ['/'], // List of routes to prerender.
renderer: new JSDOMRenderer()
})
]
}
...
If you wanted, it’s fairly trivial to implement your own renderer as well. See the official documentation for more information.
You don’t always want to render the page right off. Sometimes you want to wait for something to happen.
All three renderers can can wait for three different triggers:
Wait for an element to exist:
renderer: new PuppeteerRenderer({
// Wait to render until the element specified is detected with document.querySelector.
renderAfterElementExists: '#app'
})
Wait for a document event to be fired:
You can trigger an event in your app with document.dispatchEvent(new Event('my-document-event'))
renderer: new PuppeteerRenderer({
// Wait to render until a specified event is fired on the document.
renderAfterDocumentEvent: 'my-document-event'
})
Wait for a specified amount of time:
renderer: new PuppeteerRenderer({
// Renders after 5000 milliseconds. (5 seconds.)
renderAfterTime: 5000
})
The renderers can also inject a variable into the global scope before your app scripts run. This is useful if you need to pass something to your app or change behavior depending on whether or not the page is being prerendered.
renderer: new PuppeteerRenderer({
// injectProperty: '__PRERENDER_INJECTED'
// You can change the property added to window. The default is window.__PRERENDER_INJECTED.
// Inject can be any object that is JSON.stringify-able.
inject: {
nestLocation: 'bayou'
}
})
Now in your app, window.__PRERENDER_INJECTED.nestLocation === 'bayou'
That’s about it for now! Enjoy pre-rendering!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!