Skip to content

Commit 865e266

Browse files
committed
complete ssr guide first draft
1 parent 32b0a51 commit 865e266

File tree

1 file changed

+307
-6
lines changed

1 file changed

+307
-6
lines changed

src/guide/ssr.md

Lines changed: 307 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,41 @@ type: guide
44
order: 23
55
---
66

7+
## Do You Need SSR?
8+
9+
Before diving into SSR, let's explore what it actually does for you and when you might need it.
10+
11+
### SEO
12+
13+
Google and Bing can index synchronous JavaScript applications just fine. _Synchronous_ being the key word there. If your app starts with a loading spinner, then fetches content via Ajax, the crawler will not wait for you to finish.
14+
15+
This means if you have content fetched asynchronously on pages where SEO is important, SSR might be necessary.
16+
17+
### Clients with a Slow Internet
18+
19+
Users might come to your site from a remote area with slow Internet - or just with a bad cell connection. In these cases, you'll want to minimize the number and size of requests necessary for users to see basic content.
20+
21+
You can use [Webpack's code splitting](https://webpack.github.io/docs/code-splitting.html) to avoid forcing users to download your entire application to view a single page, but it still won't be as performant as downloading a single, pre-rendered HTML file.
22+
23+
### Clients with an Old (or Simply No) JavaScript Engine
24+
25+
For some demographics or areas of the world, using a computer from 1998 to access the Internet might be the only option. While Vue only works with IE9+, you may still want to deliver basic content to those on older browsers - or to hipster hackers using [Lynx](http://lynx.browser.org/) in the terminal.
26+
27+
### SSR vs Prerendering
28+
29+
If you're only investigating SSR to improve the SEO of a handful of marketing pages (e.g. `/`, `/about`, `/contact`, etc), then you probably want __prerendering__ instead. Rather than using a web server to compile HTML on-the-fly, prerendering simply generates static HTML files for specific routes at build time. The advantage is setting up prerendering is much simpler and allows you to keep your frontend as a fully static site.
30+
31+
If you're using Webpack, you can easily add prerendering with the [prerender-spa-plugin](https://github.com/chrisvfritz/prerender-spa-plugin). It's been extensively tested with Vue apps - and in fact, the creator is a member of the Vue core team.
32+
733
## Hello World
834

9-
Server-side rendering (i.e. SSR) sounds complex, but a simple node script demoing the feature requires only 3 steps:
35+
If you've gotten this far, you're ready to see SSR in action. It sounds complex, but a simple node script demoing the feature requires only 3 steps:
1036

1137
``` js
1238
// Step 1: Create a Vue instance
1339
var Vue = require('vue')
14-
var vm = new Vue({
15-
render: function(h) {
40+
var app = new Vue({
41+
render: function (h) {
1642
return h('p', 'hello world')
1743
}
1844
})
@@ -21,13 +47,288 @@ var vm = new Vue({
2147
var renderer = require('vue-server-renderer').createRenderer()
2248

2349
// Step 3: Render the Vue instance to HTML
24-
renderer.renderToString(vm, function (error, html) {
50+
renderer.renderToString(app, function (error, html) {
2551
if (error) throw error
2652
console.log(html)
2753
// => <p server-rendered="true">hello world</p>
2854
})
2955
```
3056

31-
Not so scary, right? In the rest of this guide, we'll give you a broad overview of how SSR works with the most common use cases. Once you understand the basics, we'll share some more detailed documentation and advanced examples to help you handle any edge cases.
57+
Not so scary, right? Of course, this example is much simpler than most applications. We don't yet have to worry about:
58+
59+
- A Web Server
60+
- Response Streaming
61+
- Component Caching
62+
- A Build Process
63+
- Routing
64+
- Vuex State Hydration
65+
66+
In the rest of this guide, we'll walk through how to work with some of these features. Once you understand the basics, we'll then direct you to more detailed documentation and advanced examples to help you handle edge cases.
67+
68+
## Simple SSR with the Express Web Server
69+
70+
It's kind of a stretch to call it "server-side rendering" when we don't actually have a web server, so let's fix that. We'll build a very simple SSR app, using only ES5 and without any build step or Vue plugins.
71+
72+
We'll start off with an app that just tells the user how many seconds they've been on the page:
73+
74+
``` js
75+
new Vue({
76+
template: '<div>You have been here for {{ counter }} seconds.</div>',
77+
data: {
78+
counter: 0
79+
},
80+
created: function () {
81+
var vm = this
82+
setInterval(function () {
83+
vm.counter += 1
84+
}, 1000)
85+
}
86+
})
87+
```
88+
89+
To adapt this for SSR, there are a few modifications we'll have to make, so that it will work both in the browser and within node:
90+
91+
- When in the browser, add an instance of our app to the global context (i.e. `window`), so that we can mount it.
92+
- When in node, export a factory function so that we can create a fresh instance of the app for every request.
93+
94+
Accomplishing this requires a little boilerplate:
95+
96+
``` js
97+
// assets/app.js
98+
(function () { 'use strict'
99+
var createApp = function () {
100+
// ---------------------
101+
// BEGIN NORMAL APP CODE
102+
// ---------------------
103+
104+
// Main Vue instance must be returned and have a root
105+
// node with the id "app", so that the client-side
106+
// version can take over once it loads.
107+
return new Vue({
108+
template: '<div id="app">You have been here for {{ counter }} seconds.</div>',
109+
data: {
110+
counter: 0
111+
},
112+
created: function () {
113+
var vm = this
114+
setInterval(function () {
115+
vm.counter += 1
116+
}, 1000)
117+
}
118+
})
119+
120+
// -------------------
121+
// END NORMAL APP CODE
122+
// -------------------
123+
}
124+
if (typeof module !== 'undefined' && module.exports) {
125+
module.exports = createApp
126+
} else {
127+
this.app = createApp()
128+
}
129+
}).call(this)
130+
```
131+
132+
Now that we have our application code, let's put together an `index.html` file:
133+
134+
``` html
135+
<!-- index.html -->
136+
<!DOCTYPE html>
137+
<html>
138+
<head>
139+
<title>My Vue App</title>
140+
<script src="/assets/vue.js"></script>
141+
</head>
142+
<body>
143+
<div id="app"></div>
144+
<script src="/assets/app.js"></script>
145+
<script>app.$mount('#app')</script>
146+
</body>
147+
</html>
148+
```
149+
150+
As long as the referenced `assets` directory contains the `app.js` file we created earlier, as well as a `vue.js` file with Vue, we should now have a working single-page application!
151+
152+
Then to get it working with server-side rendering, there's just one more step - the web server:
153+
154+
``` js
155+
// server.js
156+
'use strict'
157+
158+
var fs = require('fs')
159+
var path = require('path')
160+
161+
// Define global Vue for server-side app.js
162+
global.Vue = require('vue')
163+
164+
// Get the HTML layout
165+
var layout = fs.readFileSync('./index.html', 'utf8')
166+
167+
// Create a renderer
168+
var renderer = require('vue-server-renderer').createRenderer()
169+
170+
// Create an express server
171+
var express = require('express')
172+
var server = express()
173+
174+
// Serve files from the assets directory
175+
server.use('/assets', express.static(
176+
path.resolve(__dirname, 'assets')
177+
))
178+
179+
// Handle all GET requests
180+
server.get('*', function (request, response) {
181+
// Render our Vue app to a string
182+
renderer.renderToString(
183+
// Create an app instance
184+
require('./assets/app')(),
185+
// Handle the rendered result
186+
function (error, html) {
187+
// If an error occurred while rendering...
188+
if (error) {
189+
// Log the error in the console
190+
console.error(error)
191+
// Tell the client something went wrong
192+
return response
193+
.status(500)
194+
.send('Server Error')
195+
}
196+
// Send the layout with the rendered app's HTML
197+
response.send(layout.replace('<div id="app"></div>', html))
198+
}
199+
)
200+
})
201+
202+
// Listen on port 5000
203+
server.listen(5000, function (error) {
204+
if (error) throw error
205+
console.log('Server is running at localhost:5000')
206+
})
207+
```
208+
209+
And that's it! Here's [the full application](https://github.com/chrisvfritz/vue-ssr-demo-simple), in case you'd like to clone it and experiment further. Once you have it running locally, you can confirm that server-side rendering really is working by right-clickig on the page and selecting `View Page Source` (or similar). You should see this in the body:
210+
211+
``` html
212+
<div id="app" server-rendered="true">You have been here for 0 seconds&period;</div>
213+
```
214+
215+
instead of:
216+
217+
``` html
218+
<div id="app"></div>
219+
```
220+
221+
## Response Streaming
222+
223+
Vue also supports rendering to a __stream__, which is preferred for web servers that support streaming. This allows HTML to be written to the response _as it's generated_, rather than all at once at the end. The result is requests are served faster, with no downsides!
224+
225+
To adapt our app from the previous section for streaming, we can simply replace the `server.get('*', ...)` block with the following:
226+
227+
``` js
228+
// Split the layout into two sections of HTML
229+
var layoutSections = layout.split('<div id="app"></div>')
230+
var preAppHTML = layoutSections[0]
231+
var postAppHTML = layoutSections[1]
232+
233+
// Handle all GET requests
234+
server.get('*', function (request, response) {
235+
// Render our Vue app to a stream
236+
var stream = renderer.renderToStream(require('./assets/app')())
237+
238+
// Write the pre-app HTML to the response
239+
response.write(preAppHTML)
240+
241+
// Whenever new chunks are rendered...
242+
stream.on('data', function (chunk) {
243+
// Write the chunk to the response
244+
response.write(chunk)
245+
})
246+
247+
// When all chunks are rendered...
248+
stream.on('end', function () {
249+
// Write the post-app HTML to the response
250+
response.end(postAppHTML)
251+
})
252+
253+
// If an error occurs while rendering...
254+
stream.on('error', function (error) {
255+
// Log the error in the console
256+
console.error(error)
257+
// Tell the client something went wrong
258+
return response
259+
.status(500)
260+
.send('Server Error')
261+
})
262+
})
263+
```
264+
265+
As you can see, it's not much more complicated than the previous version, even if streams may be conceptually new to you. We just:
266+
267+
1. Set up the stream
268+
2. Write the HTML that comes before the app to the response
269+
3. Write the app HTML to the response as it becomes available
270+
4. Write the HTML that comes after the app to the response and end it
271+
5. Handle any errors
272+
273+
## Component Caching
274+
275+
Vue's SSR is very fast by default, but you can further improve performance by caching rendered components. This should be considered an advanced feature however, as caching the wrong components (or the right components with the wrong key) could lead to misrendering your app. Specifically:
276+
277+
<p class="tip">You should not cache a component containing child components that rely on global state (e.g. from a vuex store). If you do, those child components (and in fact, the entire sub-tree) will be cached as well. Be especially wary with components that accept slots/children.</p>
278+
279+
### Setup
280+
281+
With that warning out of the way, here's how you cache components.
282+
283+
First, you'll need to provide your renderer with a [cache object](https://www.npmjs.com/package/vue-server-renderer#cache). Here's a simple example using [lru-cache](https://github.com/isaacs/node-lru-cache):
284+
285+
``` js
286+
var createRenderer = require('vue-server-renderer').createRenderer
287+
var lru = require('lru-cache')
288+
289+
var renderer = createRenderer({
290+
cache: lru({
291+
max: 10000000,
292+
length: function (n, key) {
293+
return n.length
294+
}
295+
})
296+
})
297+
```
298+
299+
That will cache up to 10 million characters of HTML (10MB assuming 1 byte per character). See [the lru-cache options](https://github.com/isaacs/node-lru-cache#options) for other configurations.
300+
301+
Then for components you want to cache, you must provide them with:
302+
303+
- a unique `name`
304+
- a `serverCacheKey` function, returning a unique key scoped to the component
305+
306+
For example:
307+
308+
``` js
309+
Vue.component({
310+
name: 'list-item',
311+
template: '<li>{{ item.name }}</li>',
312+
props: ['item'],
313+
serverCacheKey: function (props) {
314+
return props.item.type + '::' + props.item.id
315+
}
316+
})
317+
```
318+
319+
### Ideal Components for Caching
320+
321+
Any "pure" component can be safely cached - that is, any component that is guaranteed to generate the same HTML given the same props. Common examples of these include:
322+
323+
- Static components (i.e. they always generate the same HTML, so the `serverCacheKey` function can just return `true`)
324+
- List item components (when part of large lists, caching these can significantly improve performance)
325+
- Generic UI components (e.g. buttons, alerts, etc - at least those that accept content through props rather than slots/children)
326+
327+
## Build Process, Routing, and Vuex State Hydration
328+
329+
By now, you should understand the fundamental concepts behind server-side rendering. However, as you introduce a build process, routing, and vuex, each introduces its own considerations.
330+
331+
To truly master server-side rendering in complex applications, we recommend a deep dive into the following resources:
32332

33-
## !!TODO: Finish this guide
333+
- [vue-server-renderer docs](https://www.npmjs.com/package/vue-server-renderer#api): more details on topics covered here, as well as documentation of more advanced topics, such as [preventing cross-request contamination](https://www.npmjs.com/package/vue-server-renderer#why-use-bundlerenderer) and [adding a separate server build](https://www.npmjs.com/package/vue-server-renderer#creating-the-server-bundle)
334+
- [vue-hackernews-2.0](https://github.com/vuejs/vue-hackernews-2.0): the definitive example of integrating all major Vue libraries and concepts in a single application

0 commit comments

Comments
 (0)