Build Configuration
We will assume you already know how to configure webpack for a client-only project. The config for an SSR project will be largely similar, but we suggest breaking the config into three files: base, client and server. The base config contains config shared for both environments, such as output path, aliases, and loaders. The server config and client config can simply extend the base config using webpack-merge (opens new window).
Server Config
The server config is meant for generating the server bundle that will be passed to createBundleRenderer
. It should look 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, {
// Point entry to your app's server entry file
entry: '/path/to/entry-server.js',
// This allows webpack to handle dynamic imports in a Node-appropriate
// fashion, and also tells `vue-loader` to emit server-oriented code when
// compiling Vue components.
target: 'node',
// For bundle renderer source map support
devtool: 'source-map',
// This tells the server bundle to use Node-style exports
output: {
libraryTarget: 'commonjs2'
},
// https://webpack.js.org/configuration/externals/#function
// https://github.com/liady/webpack-node-externals
// Externalize app dependencies. This makes the server build much faster
// and generates a smaller bundle file.
externals: nodeExternals({
// do not externalize dependencies that need to be processed by webpack.
// you can add more file types here e.g. raw *.vue files
// you should also whitelist deps that modifies `global` (e.g. polyfills)
whitelist: /\.css$/
}),
// This is the plugin that turns the entire output of the server build
// into a single JSON file. The default file name will be
// `vue-ssr-server-bundle.json`
plugins: [
new VueSSRServerPlugin()
]
})
After vue-ssr-server-bundle.json
has been generated, simply pass the file path to createBundleRenderer
:
const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer('/path/to/vue-ssr-server-bundle.json', {
// ...other renderer options
})
Alternatively, you can also pass the bundle as an Object to createBundleRenderer
. This is useful for hot-reload during development - see the HackerNews demo for a reference setup (opens new window).
Externals Caveats
Notice that in the externals
option we are whitelisting CSS files. This is because CSS imported from dependencies should still be handled by webpack. If you are importing any other types of files that also rely on webpack (e.g. *.vue
, *.sass
), you should add them to the whitelist as well.
If you are using runInNewContext: 'once'
or runInNewContext: true
, then you also need to whitelist polyfills that modify global
, e.g. babel-polyfill
. This is because when using the new context mode, code inside a server bundle has its own global
object. Since you don't really need it on the server when using Node 7.6+, it's actually easier to just import it in the client entry.
Client Config
The client config can remain largely the same with the base config. Obviously you need to point entry
to your client entry file. Aside from that, if you are using CommonsChunkPlugin
, make sure to use it only in the client config because the server bundle requires a single entry chunk.
clientManifest
Generating requires version 2.3.0+
In addition to the server bundle, we can also generate a client build manifest. With the client manifest and the server bundle, the renderer now has information of both the server and client builds, so it can automatically infer and inject preload / prefetch directives (opens new window) and css links / script tags into the rendered HTML.
The benefits are two-fold:
It can replace
html-webpack-plugin
for injecting the correct asset URLs when there are hashes in your generated filenames.When rendering a bundle that leverages webpack's on-demand code splitting features, we can ensure the optimal chunks are preloaded / prefetched, and also intelligently inject
<script>
tags for needed async chunks to avoid waterfall requests on the client, thus improving TTI (time-to-interactive).
To make use of the client manifest, the client config would look something like this:
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: this splits the webpack runtime into a leading chunk
// so that async chunks can be injected right after it.
// this also enables better caching for your app/vendor code.
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// This plugins generates `vue-ssr-client-manifest.json` in the
// output directory.
new VueSSRClientPlugin()
]
})
You can then use the generated client manifest, together with a page template:
const { createBundleRenderer } = require('vue-server-renderer')
const template = require('fs').readFileSync('/path/to/template.html', 'utf-8')
const serverBundle = require('/path/to/vue-ssr-server-bundle.json')
const clientManifest = require('/path/to/vue-ssr-client-manifest.json')
const renderer = createBundleRenderer(serverBundle, {
template,
clientManifest
})
With this setup, your server-rendered HTML for a build with code-splitting will look something like this (everything auto-injected):
<html>
<head>
<!-- chunks used for this render will be preloaded -->
<link rel="preload" href="/manifest.js" as="script">
<link rel="preload" href="/main.js" as="script">
<link rel="preload" href="/0.js" as="script">
<!-- unused async chunks will be prefetched (lower priority) -->
<link rel="prefetch" href="/1.js" as="script">
</head>
<body>
<!-- app content -->
<div data-server-rendered="true"><div>async</div></div>
<!-- manifest chunk should be first -->
<script src="/manifest.js"></script>
<!-- async chunks injected before main chunk -->
<script src="/0.js"></script>
<script src="/main.js"></script>
</body>
</html>
Manual Asset Injection
By default, asset injection is automatic when you provide the template
render option. But sometimes you might want finer-grained control over how assets are injected into the template, or maybe you are not using a template at all. In such a case, you can pass inject: false
when creating the renderer and manually perform asset injection.
In the renderToString
callback, the context
object you passed in will expose the following methods:
context.renderStyles()
This will return inline
<style>
tags containing all the critical CSS collected from the*.vue
components used during the render. See CSS Management for more details.If a
clientManifest
is provided, the returned string will also contain<link rel="stylesheet">
tags for webpack-emitted CSS files (e.g. CSS extracted withextract-text-webpack-plugin
or imported withfile-loader
)context.renderState(options?: Object)
This method serializes
context.state
and returns an inline script that embeds the state aswindow.__INITIAL_STATE__
.The context state key and window state key can both be customized by passing an options object:
context.renderState({ contextKey: 'myCustomState', windowKey: '__MY_STATE__' }) // -> <script>window.__MY_STATE__={...}</script>
context.renderScripts()
- requires
clientManifest
This method returns the
<script>
tags needed for the client application to boot. When using async code-splitting in the app code, this method will intelligently infer the correct async chunks to include.- requires
context.renderResourceHints()
- requires
clientManifest
This method returns the
<link rel="preload/prefetch">
resource hints needed for the current rendered page. By default it will:- Preload the JavaScript and CSS files needed by the page
- Prefetch async JavaScript chunks that might be needed later
Preloaded files can be further customized with the
shouldPreload
option.- requires
context.getPreloadFiles()
- requires
clientManifest
This method does not return a string - instead, it returns an Array of file objects representing the assets that should be preloaded. This can be used to programmatically perform HTTP/2 server push.
- requires
Since the template
passed to createBundleRenderer
will be interpolated using context
, you can make use of these methods inside the template (with inject: false
):
<html>
<head>
<!-- use triple mustache for non-HTML-escaped interpolation -->
{{{ renderResourceHints() }}}
{{{ renderStyles() }}}
</head>
<body>
<!--vue-ssr-outlet-->
{{{ renderState() }}}
{{{ renderScripts() }}}
</body>
</html>
If you are not using template
at all, you can concatenate the strings yourself.