vue-meta

Manage page meta info in Vue 2.0 components. SSR + Streaming supported. Inspired by react-helmet.

Standard - JavaScript Style

github release npm version Build Status codecov
dependencies Status devDependencies Status
npm downloads Gitter

```html ``` # Table of Contents - [Description](#description) - [Installation](#installation) - [Yarn](#yarn) - [NPM](#npm) - [CDN](#cdn) - [Usage](#usage) - [Step 1: Preparing the plugin](#step-1-preparing-the-plugin) - [Options](#options) - [Step 2: Server Rendering (Optional)](#step-2-server-rendering-optional) - [Step 2.1: Exposing `$meta` to `bundleRenderer`](#step-21-exposing-meta-to-bundlerenderer) - [Step 2.2: Populating the document meta info with `inject()`](#step-22-populating-the-document-meta-info-with-inject) - [Simple Rendering with `renderToString()`](#simple-rendering-with-rendertostring) - [Streaming Rendering with `renderToStream()`](#streaming-rendering-with-rendertostream) - [Step 3: Start defining `metaInfo`](#step-3-start-defining-metainfo) - [Recognized `metaInfo` Properties](#recognized-metainfo-properties) - [`title` (String)](#title-string) - [`titleTemplate` (String | Function)](#titletemplate-string--function) - [`htmlAttrs` (Object)](#htmlattrs-object) - [`headAttrs` (Object)](#headattrs-object) - [`bodyAttrs` (Object)](#bodyattrs-object) - [`base` (Object)](#base-object) - [`meta` ([Object])](#meta-object) - [`link` ([Object])](#link-object) - [`style` ([Object])](#style-object) - [`script` ([Object])](#script-object) - [`noscript` ([Object])](#noscript-object) - [`__dangerouslyDisableSanitizers` ([String])](#__dangerouslydisablesanitizers-string) - [`__dangerouslyDisableSanitizersByTagID` ({[String]})](#__dangerouslydisablesanitizersbytagid-string) - [`changed` (Function)](#changed-function) - [How `metaInfo` is Resolved](#how-metainfo-is-resolved) - [Lists of Tags](#lists-of-tags) - [Performance](#performance) - [How to prevent the update on the initial page render](#how-to-prevent-the-update-on-the-initial-page-render) - [FAQ](#faq) - [How do I use component props and/or component data in `metaInfo`?](#how-do-i-use-component-props-andor-component-data-in-metainfo) - [How do I populate `metaInfo` from the result of an asynchronous action?](#how-do-i-populate-metainfo-from-the-result-of-an-asynchronous-action) - [Why doesn't `vue-meta` support `jsnext:main`?](#why-doesnt-vue-meta-support-jsnextmain) - [Examples](#examples) # Description `vue-meta` is a [Vue 2.0](https://vuejs.org) plugin that allows you to manage your app's meta information, much like [`react-helmet`](https://github.com/nfl/react-helmet) does for React. However, instead of setting your data as props passed to a proprietary component, you simply export it as part of your component's data using the `metaInfo` property. These properties, when set on a deeply nested component, will cleverly overwrite their parent components' `metaInfo`, thereby enabling custom info for each top-level view as well as coupling meta info directly to deeply nested subcomponents for more maintainable code. # Installation ### Yarn ```sh $ yarn add vue-meta ``` ### NPM ```sh $ npm install vue-meta --save ``` ### CDN Use the links below - if you want a previous version, check the instructions at https://unpkg.com. **Uncompressed:** ```html ``` **Minified:** ```html ``` # Usage ## Step 1: Preparing the plugin > This step is optional if you don't need SSR and `Vue` is available as a global variable. `vue-meta` will install itself in this case. In order to use this plugin, you first need to pass it to `Vue.use` - if you're not rendering on the server-side, your JS entry file will suffice. If you are rendering on the server, then place it in a file that runs both on the server and on the client before your root instance is mounted. If you're using [`vue-router`](https://github.com/vuejs/vue-router), then your main `router.js` file is a good place: **router.js:** ```js import Vue from 'vue' import Router from 'vue-router' import Meta from 'vue-meta' Vue.use(Router) Vue.use(Meta) export default new Router({ ... }) ``` #### Options `vue-meta` allows a few custom options: ```js Vue.use(Meta, { keyName: 'metaInfo', // the component option name that vue-meta looks for meta info on. attribute: 'data-vue-meta', // the attribute name vue-meta adds to the tags it observes ssrAttribute: 'data-vue-meta-server-rendered', // the attribute name that lets vue-meta know that meta info has already been server-rendered tagIDKeyName: 'vmid' // the property name that vue-meta uses to determine whether to overwrite or append a tag }) ``` If you don't care about server-side rendering, you can skip straight to [step 3](#step-3-start-defining-metainfo). Otherwise, continue. :smile: ## Step 2: Server Rendering (Optional) If you have an isomorphic/universal webapp, you'll likely want to render your metadata on the server side as well. Here's how. ### Step 2.1: Exposing `$meta` to `bundleRenderer` You'll need to expose the results of the `$meta` method that `vue-meta` adds to the Vue instance to the bundle render context before you can begin injecting your meta information. You'll need to do this in your server entry file: **server-entry.js:** ```js import app from './app' const router = app.$router const meta = app.$meta() // here export default (context) => { router.push(context.url) context.meta = meta // and here return app } ``` ### Step 2.2: Populating the document meta info with `inject()` All that's left for you to do now before you can begin using `metaInfo` options in your components is to make sure they work on the server by `inject`-ing them so you can call `text()` on each item to render out the necessary info. You have two methods at your disposal: #### Simple Rendering with `renderToString()` Considerably the easiest method to wrap your head around is if your Vue server markup is rendered out as a string: **server.js:** ```js app.get('*', (req, res) => { const context = { url: req.url } renderer.renderToString(context, (error, html) => { if (error) return res.send(error.stack) const bodyOpt = { body: true } const { title, htmlAttrs, headAttrs, bodyAttrs, link, style, script, noscript, meta } = context.meta.inject() return res.send(` ${meta.text()} ${title.text()} ${link.text()} ${style.text()} ${script.text()} ${noscript.text()} ${html} ${script.text(bodyOpt)} `) }) }) ``` If you are using a separate template file, edit your head tag with ```html {{{ meta.inject().title.text() }}} {{{ meta.inject().meta.text() }}} ``` Notice the use of `{{{` to avoid double escaping. Be extremely cautious when you use `{{{` with `__dangerouslyDisableSanitizers`. #### Streaming Rendering with `renderToStream()` A little more complex, but well worth it, is to instead stream your response. `vue-meta` supports streaming with no effort (on it's part :stuck_out_tongue_winking_eye:) thanks to Vue's clever `bundleRenderer` context injection: **server.js** ```js app.get('*', (req, res) => { const context = { url: req.url } const renderStream = renderer.renderToStream(context) renderStream.once('data', () => { const bodyOpt = { body: true } const { title, htmlAttrs, headAttrs, bodyAttrs, link, style, script, noscript, meta } = context.meta.inject() res.write(` ${meta.text()} ${title.text()} ${link.text()} ${style.text()} ${script.text()} ${noscript.text()} `) }) renderStream.on('data', (chunk) => { res.write(chunk) }) renderStream.on('end', () => { res.end(` ${script.text(bodyOpt)} `) }) renderStream.on('error', (error) => res.status(500).end(`
${error.stack}
`)) }) ``` ## Step 3: Start defining `metaInfo` In any of your components, define a `metaInfo` property: **App.vue:** ```html ``` **Home.vue** ```html ``` **About.vue** ```html ``` ### Recognized `metaInfo` Properties #### `title` (String) Maps to the inner-text value of the `` element. ```js { metaInfo: { title: 'Foo Bar' } } ``` ```html <title>Foo Bar ``` #### `titleTemplate` (String | Function) The value of `title` will be injected into the `%s` placeholder in `titleTemplate` before being rendered. The original title will be available on `metaInfo.titleChunk`. ```js { metaInfo: { title: 'Foo Bar', titleTemplate: '%s - Baz' } } ``` ```html Foo Bar - Baz ``` The property can also be a function (from [v1.2.0](https://github.com/nuxt/vue-meta/releases/tag/v1.2.0)): ```js titleTemplate: (titleChunk) => { // If undefined or blank then we don't need the hyphen return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'; } ``` #### `htmlAttrs` (Object) Each **key:value** maps to the equivalent **attribute:value** of the `` element. ```js { metaInfo: { htmlAttrs: { foo: 'bar', amp: undefined } } } ``` ```html ``` #### `headAttrs` (Object) Each **key:value** maps to the equivalent **attribute:value** of the `` element. ```js { metaInfo: { headAttrs: { foo: 'bar' } } } ``` ```html ``` #### `bodyAttrs` (Object) Each **key:value** maps to the equivalent **attribute:value** of the `` element. ```js { metaInfo: { bodyAttrs: { bar: 'baz' } } } ``` ```html Foo Bar ``` #### `base` (Object) Maps to a newly-created `` element, where object properties map to attributes. ```js { metaInfo: { base: { target: '_blank', href: '/' } } } ``` ```html ``` #### `meta` ([Object]) Each item in the array maps to a newly-created `` element, where object properties map to attributes. ```js { metaInfo: { meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' } ] } } ``` ```html ``` Since v1.5.0, you can now set up meta templates that work similar to the titleTemplate: ```js { metaInfo: { meta: [ { charset: 'utf-8' }, { 'property': 'og:title', 'content': 'Test title', 'template': chunk => `${chunk} - My page`, //or as string template: '%s - My page', 'vmid': 'og:title' } ] } } ``` ```html ``` #### `link` ([Object]) Each item in the array maps to a newly-created `` element, where object properties map to attributes. ```js { metaInfo: { link: [ { rel: 'stylesheet', href: '/css/index.css' }, { rel: 'favicon', href: 'favicon.ico' } ] } } ``` ```html ``` #### `style` ([Object]) Each item in the array maps to a newly-created ` ``` #### `script` ([Object]) Each item in the array maps to a newly-created ` ``` :warning: You have to disable sanitizers so the content of `innerHTML` won't be escaped. Please refer to [\__dangerouslyDisableSanitizers](#__dangerouslydisablesanitizers-string) section below for more info on related risks. ```js { metaInfo: { script: [ { innerHTML: '{ "@context": "http://schema.org" }', type: 'application/ld+json' } ], __dangerouslyDisableSanitizers: ['script'], } } ``` ```html ``` If your browser doesn't support `defer` or any other reason, you want to put ` ``` **PostContainer.vue:** ```html ``` ## How do I populate `metaInfo` from the result of an asynchronous action? `vue-meta` will do this for you automatically when your component state changes. Just make sure that you're using the function form of `metaInfo`: ```js { data () { return { title: 'Foo Bar Baz' } }, metaInfo () { return { title: this.title } } } ``` Check out the [vuex-async](https://github.com/nuxt/vue-meta/tree/master/examples/vuex-async) example for a far more detailed demonstration if you have doubts. Credit & Thanks for this feature goes to [Sébastien Chopin](https://github.com/Atinux). ## Why doesn't `vue-meta` support `jsnext:main`? Originally, it did - however, it caused [problems](https://github.com/nuxt/vue-meta/issues/25). Essentially, Vue [does not support](https://github.com/vuejs/vue/issues/2880) `jsnext:main`, and does not introspect for the `default` property that is transpiled from the ES2015 source, thus breaking module resolution. Given that `jsnext:main` is a non-standard property that won't stick around for long, and `vue-meta` is bundled into one file with no dynamic module internals as well as the fact that if you're using `vue-meta`, you're 99.9% likely to not be using it conditionally - the decision has been made to drop support for it entirely. If this were not the case, you would have to instruct Babel to convert `default` imports to the proper commonjs module syntax via a plugin, which is not ideal since many users in the Vue landscape write their code in TypeScript, not Babel. # Examples To run the examples; clone this repository & run `npm install` in the root directory, and then run `npm run dev`. Head to http://localhost:8080.