diff --git a/LANGS.md b/LANGS.md index 25b60e8a..f379e75a 100644 --- a/LANGS.md +++ b/LANGS.md @@ -1 +1,2 @@ * [English](en/) +* [Русский](ru/) diff --git a/ru/README.md b/ru/README.md new file mode 100644 index 00000000..084c25b9 --- /dev/null +++ b/ru/README.md @@ -0,0 +1,50 @@ +# Vue.js Server-Side Rendering Guide + +> **Note:** this guide requires the following minimum versions of Vue and supporting libraries: +> - vue & vue-server-renderer >= 2.3.0 +> - vue-router >= 2.5.0 +> - vue-loader >= 12.0.0 & vue-style-loader >= 3.0.0 + +> If you have previously used Vue 2.2 with SSR, you will notice that the recommended code structure is now [a bit different](./structure.md) (with the new [runInNewContext](./api.md#runinnewcontext) option set to `false`). Your existing app should continue to work, but it's recommended to migrate to the new recommendations. + +## What is Server-Side Rendering (SSR)? + +Vue.js is a framework for building client-side applications. By default, Vue components produce and manipulate DOM in the browser as output. However, it is also possible to render the same components into HTML strings on the server, send them directly to the browser, and finally "hydrate" the static markup into a fully interactive app on the client. + +A server-rendered Vue.js app can also be considered "isomorphic" or "universal", in the sense that the majority of your app's code runs on both the server **and** the client. + +## Why SSR? + +Compared to a traditional SPA (Single-Page Application), the advantage of SSR primarily lies in: + +- Better SEO, as the search engine crawlers will directly see the fully rendered page. + + Note that as of now, 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. This means if you have content fetched asynchronously on pages where SEO is important, SSR might be necessary. + +- Faster time-to-content, especially on slow internet or slow devices. Server-rendered markup doesn't need to wait until all JavaScript has been downloaded and executed to be displayed, so your user will see a fully-rendered page sooner. This generally results in better user experience, and can be critical for applications where time-to-content is directly associated with conversion rate. + +There are also some trade-offs to consider when using SSR: + +- Development constraints. Browser-specific code can only be used inside certain lifecycle hooks; some external libraries may need special treatment to be able to run in a server-rendered app. + +- More involved build setup and deployment requirements. Unlike a fully static SPA that can be deployed on any static file server, a server-rendered app requires an environment where a Node.js server can run. + +- More server-side load. Rendering a full app in Node.js is obviously going to be more CPU-intensive than just serving static files, so if you expect high traffic, be prepared for corresponding server load and wisely employ caching strategies. + +Before using SSR for your app, the first question you should ask it whether you actually need it. It mostly depends on how important time-to-content is for your app. For example, if you are building an internal dashboard where an extra few hundred milliseconds on initial load doesn't matter that much, SSR would be an overkill. However, in cases where time-to-content is absolutely critical, SSR can help you achieve the best possible initial load performance. + +## SSR vs Prerendering + +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. + +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](https://github.com/chrisvfritz) is a member of the Vue core team. + +## About This Guide + +This guide is focused on server-rendered Single-Page Applications using Node.js as the server. Mixing Vue SSR with other backend setups is a topic of its own and is not covered in this guide. + +This guide will be very in-depth and assumes you are already familiar with Vue.js itself, and have decent working knowledge of Node.js and webpack. If you prefer a higher-level solution that provides a smooth out-of-the-box experience, you should probably give [Nuxt.js](http://nuxtjs.org/) a try. It's built upon the same Vue stack but abstracts away a lot of the boilerplate, and provides some extra features such as static site generation. However, it may not suit your use case if you need more direct control of your app's structure. Regardless, it would still be beneficial to read through this guide to better understand how things work together. + +As you read along, it would be helpful to refer to the official [HackerNews Demo](https://github.com/vuejs/vue-hackernews-2.0/), which makes use of most of the techniques covered in this guide. + +Finally, note that the solutions in this guide are not definitive - we've found them to be working well for us, but that doesn't mean they cannot be improved. They might get revised in the future - and feel free to contribute by submitting pull requests! diff --git a/ru/SUMMARY.md b/ru/SUMMARY.md new file mode 100644 index 00000000..efd9eaf7 --- /dev/null +++ b/ru/SUMMARY.md @@ -0,0 +1,27 @@ +- [Basic Usage](basic.md) +- [Writing Universal Code](universal.md) +- [Source Code Structure](structure.md) +- [Routing and Code-Splitting](routing.md) +- [Data Pre-fetching and State](data.md) +- [Client Side Hydration](hydration.md) +- [Introducing Bundle Renderer](bundle-renderer.md) +- [Build Configuration](build-config.md) +- [CSS Management](css.md) +- [Head Management](head.md) +- [Caching](caching.md) +- [Streaming](streaming.md) +- [API Reference](api.md) + - [createRenderer](api.md#createrendereroptions) + - [createBundleRenderer](api.md#createbundlerendererbundle-options) + - [Class: Renderer](api.md#class-renderer) + - [Class: BundleRenderer](api.md#class-bundlerenderer) + - [Renderer Options](api.md#renderer-options) + - [template](api.md#template) + - [clientManifest](api.md#clientmanifest) + - [inject](api.md#inject) + - [shouldPreload](api.md#shouldpreload) + - [runInNewContext](api.md#runinnewcontext) + - [basedir](api.md#basedir) + - [cache](api.md#cache) + - [directives](api.md#directives) + - [Webpack Plugins](api.md#webpack-plugins) diff --git a/ru/api.md b/ru/api.md new file mode 100644 index 00000000..8c82f6b5 --- /dev/null +++ b/ru/api.md @@ -0,0 +1,227 @@ +# API Reference + +## `createRenderer([options])` + +Create a [`Renderer`](#class-renderer) instance with (optional) [options](#renderer-options). + +``` js +const { createRenderer } = require('vue-server-renderer') +const renderer = createRenderer({ ... }) +``` + +## `createBundleRenderer(bundle[, options])` + +Create a [`BundleRenderer`](#class-bundlerenderer) instance with a server bundle and (optional) [options](#renderer-options). + +``` js +const { createBundleRenderer } = require('vue-server-renderer') +const renderer = createBundleRenderer(serverBundle, { ... }) +``` + +The `serverBundle` argument can be one of the following: + +- An absolute path to generated bundle file (`.js` or `.json`). Must start with `/` to be treated as a file path. + +- A bundle object generated by webpack + `vue-server-renderer/server-plugin`. + +- A string of JavaScript code (not recommended). + +See [Introducing the Server Bundle](./bundle-renderer.md) and [Build Configuration](./build-config.md) for more details. + +## `Class: Renderer` + +- #### `renderer.renderToString(vm[, context], callback)` + + Render a Vue instance to string. The context object is optional. The callback is a typical Node.js style callback where the first argument is the error and the second argument is the rendered string. + +- #### `renderer.renderToStream(vm[, context])` + + Render a Vue instance to a Node.js stream. The context object is optional. See [Streaming](./streaming.md) for more details. + +## `Class: BundleRenderer` + +- #### `bundleRenderer.renderToString([context, ]callback)` + + Render the bundle to a string. The context object is optional. The callback is a typical Node.js style callback where the first argument is the error and the second argument is the rendered string. + +- #### `bundleRenderer.renderToStream([context])` + + Render the bundle to a Node.js stream. The context object is optional. See [Streaming](./streaming.md) for more details. + +## Renderer Options + +- #### `template` + + Provide a template for the entire page's HTML. The template should contain a comment `` which serves as the placeholder for rendered app content. + + The template also supports basic interpolation using the render context: + + - Use double-mustache for HTML-escaped interpolation; + - Use triple-mustache for non-HTML-escaped interpolation. + + The template automatically injects appropriate content when certain data is found on the render context: + + - `context.head`: (string) any head markup that should be injected into the head of the page. + + - `context.styles`: (string) any inline CSS that should be injected into the head of the page. Note this property will be automatically populated if using `vue-loader` + `vue-style-loader` for component CSS. + + - `context.state`: (Object) initial Vuex store state that should be inlined in the page as `window.__INITIAL_STATE__`. The inlined JSON is automatically sanitized with [serialize-javascript](https://github.com/yahoo/serialize-javascript) to prevent XSS. + + In addition, when `clientManifest` is also provided, the template automatically injects the following: + + - Client-side JavaScript and CSS assets needed by the render (with async chunks automatically inferred); + - Optimal `` resource hints for the rendered page. + + You can disable all automatic injections by also passing `inject: false` to the renderer. + + See also: + + - [Using a Page Template](./basic.md#using-a-page-template) + - [Manual Asset Injection](./build-config.md#manual-asset-injection) + +- #### `clientManifest` + + - 2.3.0+ + - only used in `createBundleRenderer` + + Provide a client build manifest object generated by `vue-server-renderer/server-plugin`. The client manifest provides the bundle renderer with the proper information for automatic asset injection into the HTML template. For more details, see [Generating clientManifest](./build-config.md#generating-clientmanifest). + +- #### `inject` + + - 2.3.0+ + + Controls whether to perform automatic injections when using `template`. Defaults to `true`. + + See also: [Manual Asset Injection](./build-config.md#manual-asset-injection). + +- #### `shouldPreload` + + - 2.3.0+ + + A function to control what files should have `` resource hints generated. + + By default, only JavaScript and CSS files will be preloaded, as they are absolutely needed for your application to boot. + + For other types of assets such as images or fonts, preloading too much may waste bandwidth and even hurt performance, so what to preload will be scenario-dependent. You can control precisely what to preload using the `shouldPreload` option: + + ``` js + const renderer = createBundleRenderer(bundle, { + template, + clientManifest, + shouldPreload: (file, type) => { + // type is inferred based on the file extension. + // https://fetch.spec.whatwg.org/#concept-request-destination + if (type === 'script' || type === 'style') { + return true + } + if (type === 'font') { + // only preload woff2 fonts + return /\.woff2$/.test(file) + } + if (type === 'image') { + // only preload important images + return file === 'hero.jpg' + } + } + }) + ``` + +- #### `runInNewContext` + + - 2.3.0+ + - only used in `createBundleRenderer` + + By default, for each render the bundle renderer will create a fresh V8 context and re-execute the entire bundle. This has some benefits - for example, we don't need to worry about the "stateful singleton" problem we mentioned earlier. However, this mode comes at some considerable performance cost because re-executing the bundle is expensive especially when the app gets bigger. + + This option defaults to `true` for backwards compatibility, but it is recommended to use `runInNewContext: false` whenever you can. + + See also: [Source Code Structure](./structure.md) + +- #### `basedir` + + - 2.2.0+ + - only used in `createBundleRenderer` + + Explicitly declare the base directory for the server bundle to resolve `node_modules` dependencies from. This is only needed if your generated bundle file is placed in a different location from where the externalized NPM dependencies are installed, or your `vue-server-renderer` is npm-linked into your current project. + +- #### `cache` + + Provide a [component cache](./caching.md#component-level-caching) implementation. The cache object must implement the following interface (using Flow notations): + + ``` js + type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; + }; + ``` + + A typical usage is passing in an [lru-cache](https://github.com/isaacs/node-lru-cache): + + ``` js + const LRU = require('lru-cache') + + const renderer = createRenderer({ + cache: LRU({ + max: 10000 + }) + }) + ``` + + Note that the cache object should at least implement `get` and `set`. In addition, `get` and `has` can be optionally async if they accept a second argument as callback. This allows the cache to make use of async APIs, e.g. a redis client: + + ``` js + const renderer = createRenderer({ + cache: { + get: (key, cb) => { + redisClient.get(key, (err, res) => { + // handle error if any + cb(res) + }) + }, + set: (key, val) => { + redisClient.set(key, val) + } + } + }) + ``` + +- #### `directives` + + Allows you to provide server-side implementations for your custom directives: + + ``` js + const renderer = createRenderer({ + directives: { + example (vnode, directiveMeta) { + // transform vnode based on directive binding metadata + } + } + }) + ``` + + As an example, check out [`v-show`'s server-side implementation](https://github.com/vuejs/vue/blob/dev/src/platforms/web/server/directives/show.js). + +## Webpack Plugins + +The webpack plugins are provided as standalone files and should be required directly: + +``` js +const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') +const VueSSRClientPlugin = require('vue-server-renderer/client-plugin') +``` + +The default files generated are: + +- `vue-ssr-server-bundle.json` for the server plugin; +- `vue-ssr-client-manifest.json` for the client plugin. + +The filenames can be customized when creating the plugin instances: + +``` js +const plugin = new VueSSRServerPlugin({ + filename: 'my-server-bundle.json' +}) +``` + +See [Build Configuration](./build-config.md) for more information. diff --git a/ru/basic.md b/ru/basic.md new file mode 100644 index 00000000..bc50f593 --- /dev/null +++ b/ru/basic.md @@ -0,0 +1,147 @@ +# Basic Usage + +## Installation + +``` bash +npm install vue vue-server-renderer --save +``` + +We will be using NPM throughout the guide, but feel free to use [Yarn](https://yarnpkg.com/en/) instead. + +#### Notes + +- It's recommended to use Node.js version 6+. +- `vue-server-renderer` and `vue` must have matching versions. +- `vue-server-renderer` relies on some Node.js native modules and therefore can only be used in Node.js. We may provide a simpler build that can be run in other JavaScript runtimes in the future. + +## Rendering a Vue Instance + +``` js +// Step 1: Create a Vue instance +const Vue = require('vue') +const app = new Vue({ + template: `