diff --git a/lib/common/utils.js b/lib/common/utils.js
index 997515c721..54a13cbaa1 100644
--- a/lib/common/utils.js
+++ b/lib/common/utils.js
@@ -151,24 +151,6 @@ export function flatRoutes (router, path = '', routes = []) {
return routes
}
-export function attrsStr (attrObj = {}, exclude = []) {
- return Object.keys(attrObj)
- .filter(attr => !exclude.includes(attr))
- .map(attr => {
- if (typeof attrObj[attr] !== 'string') {
- return attr
- }
-
- const val = attrObj[attr].replace('"', '\'')
-
- if (attr === 'hid') {
- attr = 'data-hid'
- }
-
- return `${attr}="${val}"`
- }).join(' ')
-}
-
export function cleanChildrenRoutes (routes, isChild = false) {
let start = -1
let routesIndex = []
diff --git a/lib/core/meta.js b/lib/core/meta.js
index 75f8b78a86..0c3fdff70a 100644
--- a/lib/core/meta.js
+++ b/lib/core/meta.js
@@ -1,5 +1,7 @@
-import { attrsStr } from 'utils'
+import Vue from 'vue'
+import VueMeta from 'vue-meta'
+import VueServerRenderer from 'vue-server-renderer'
import LRU from 'lru-cache'
export default class MetaRenderer {
@@ -7,68 +9,70 @@ export default class MetaRenderer {
this.nuxt = nuxt
this.renderer = renderer
this.options = nuxt.options
+ this.vueRenderer = VueServerRenderer.createRenderer()
this.cache = LRU({})
+
+ // Add VueMeta to Vue (this is only for SPA mode)
+ // See lib/app/index.js
+ Vue.use(VueMeta, {
+ keyName: 'head',
+ attribute: 'data-n-head',
+ ssrAttribute: 'data-n-head-ssr',
+ tagIDKeyName: 'hid'
+ })
}
- render ({ url = '/' }) {
- let head = this.cache.get(url)
-
- if (head) {
- return head
- }
-
- head = ''
-
- // Title
- if (typeof this.options.head.title === 'string') {
- head += `
${this.options.head.title || ''}`
- }
-
- // Meta
- if (Array.isArray(this.options.head.meta)) {
- this.options.head.meta.forEach(meta => {
- head += ``
+ getMeta(url) {
+ return new Promise((resolve, reject) => {
+ const vm = new Vue({
+ render: (h) => h(), // Render empty html tag
+ head: this.options.head || {}
})
- }
-
- // Links
- if (Array.isArray(this.options.head.link)) {
- this.options.head.link.forEach(link => {
- head += ``
+ this.vueRenderer.renderToString(vm, (err) => {
+ if (err) return reject(err)
+ resolve(vm.$meta().inject())
})
+ })
+ }
+
+ async render ({ url = '/' }) {
+ let meta = this.cache.get(url)
+
+ if (meta) {
+ return meta
}
- // Style
- if (Array.isArray(this.options.head.style)) {
- this.options.head.style.forEach(style => {
- head += ``
- })
+ meta = {
+ HTML_ATTRS: '',
+ BODY_ATTRS: '',
+ HEAD: ''
}
-
- // Script
- if (Array.isArray(this.options.head.script)) {
- this.options.head.script.forEach(script => {
- head += ``
- })
- }
-
+ // Get vue-meta context
+ const m = await this.getMeta(url)
+ // HTML_ATTRS
+ meta.HTML_ATTRS = 'data-n-head-ssr ' + m.htmlAttrs.text()
+ // BODY_ATTRS
+ meta.BODY_ATTRS = m.bodyAttrs.text()
+ // HEAD tags
+ meta.HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
// Resource Hints
const clientManifest = this.renderer.resources.clientManifest
if (this.options.render.resourceHints && clientManifest) {
const publicPath = clientManifest.publicPath || '/_nuxt/'
// Pre-Load initial resources
if (Array.isArray(clientManifest.initial)) {
- head += clientManifest.initial.map(r => ``).join('')
+ meta.HEAD += clientManifest.initial.map(r => ``).join('')
}
// Pre-Fetch async resources
if (Array.isArray(clientManifest.async)) {
- head += clientManifest.async.map(r => ``).join('')
+ meta.HEAD += clientManifest.async.map(r => ``).join('')
}
}
- this.cache.set(url, head)
+ // Set meta tags inside cache
+ this.cache.set(url, meta)
- return head
+ return meta
}
}
diff --git a/lib/core/meta.old.js b/lib/core/meta.old.js
new file mode 100644
index 0000000000..75f8b78a86
--- /dev/null
+++ b/lib/core/meta.old.js
@@ -0,0 +1,74 @@
+
+import { attrsStr } from 'utils'
+import LRU from 'lru-cache'
+
+export default class MetaRenderer {
+ constructor (nuxt, renderer) {
+ this.nuxt = nuxt
+ this.renderer = renderer
+ this.options = nuxt.options
+ this.cache = LRU({})
+ }
+
+ render ({ url = '/' }) {
+ let head = this.cache.get(url)
+
+ if (head) {
+ return head
+ }
+
+ head = ''
+
+ // Title
+ if (typeof this.options.head.title === 'string') {
+ head += `${this.options.head.title || ''}`
+ }
+
+ // Meta
+ if (Array.isArray(this.options.head.meta)) {
+ this.options.head.meta.forEach(meta => {
+ head += ``
+ })
+ }
+
+ // Links
+ if (Array.isArray(this.options.head.link)) {
+ this.options.head.link.forEach(link => {
+ head += ``
+ })
+ }
+
+ // Style
+ if (Array.isArray(this.options.head.style)) {
+ this.options.head.style.forEach(style => {
+ head += ``
+ })
+ }
+
+ // Script
+ if (Array.isArray(this.options.head.script)) {
+ this.options.head.script.forEach(script => {
+ head += ``
+ })
+ }
+
+ // Resource Hints
+ const clientManifest = this.renderer.resources.clientManifest
+ if (this.options.render.resourceHints && clientManifest) {
+ const publicPath = clientManifest.publicPath || '/_nuxt/'
+ // Pre-Load initial resources
+ if (Array.isArray(clientManifest.initial)) {
+ head += clientManifest.initial.map(r => ``).join('')
+ }
+
+ // Pre-Fetch async resources
+ if (Array.isArray(clientManifest.async)) {
+ head += clientManifest.async.map(r => ``).join('')
+ }
+ }
+
+ this.cache.set(url, head)
+
+ return head
+ }
+}
diff --git a/lib/core/renderer.js b/lib/core/renderer.js
index 11b201e44e..c1f4059943 100644
--- a/lib/core/renderer.js
+++ b/lib/core/renderer.js
@@ -10,7 +10,7 @@ import _ from 'lodash'
import { join, resolve } from 'path'
import fs from 'fs-extra'
import { createBundleRenderer } from 'vue-server-renderer'
-import { getContext, setAnsiColors, isUrl, attrsStr } from 'utils'
+import { getContext, setAnsiColors, isUrl } from 'utils'
import Debug from 'debug'
import Youch from '@nuxtjs/youch'
import { SourceMapConsumer } from 'source-map'
@@ -446,13 +446,12 @@ export default class Renderer extends Tapable {
// Basic response if SSR is disabled or spa data provided
const spa = context.spa || (context.res && context.res.spa)
if (this.noSSR || spa) {
- const HEAD = this.metaRenderer.render(context)
- const HTML_ATTRS = attrsStr(this.options.head.htmlAttrs)
+ const { HTML_ATTRS, BODY_ATTRS, HEAD } = await this.metaRenderer.render(context)
const APP = `${this.resources.loadingHTML}
`
const data = {
HTML_ATTRS,
- BODY_ATTRS: '',
+ BODY_ATTRS,
HEAD,
APP
}