Skip to content

Commit

Permalink
feat: expose head SEO function to use in layout (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienbaron authored and manniL committed Nov 15, 2018
1 parent d1bbc84 commit ce373c4
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 82 deletions.
72 changes: 72 additions & 0 deletions docs/seo.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,75 @@ export default {

To override SEO metadata for any page, simply declare your own `head ()` method. Have a look at [src/plugins/seo.js](/src/plugins/seo.js) if you want to copy some of **nuxt-i18n**'s logic.

## Improving performance

The default method to inject SEO metadata, while convenient, comes at a performance costs.
The `head` method is registered for every component in your app.
This means each time a component is created, the SEO metadata is recomputed for every components.

To improve performance you can use the `$nuxtI18nSeo` method in your layout instead.
It will generate i18n SEO metadata for the current context.

First you need to disable automatic SEO by setting `seo` to `false` in your configuration:

```js
// nuxt.config.js

['nuxt-i18n', {
seo: false
}]
```

Then in your app layout declare the [`head` hook](https://nuxtjs.org/api/pages-head#the-head-method) and use `$nuxtI18nSeo` inside to generate i18n SEO meta information:

```js
// layouts/default.vue

export default {
head () {
return this.$nuxtI18nSeo()
}
}
```

If you have more layouts, don't forget to add it there too.

That's it!
Now SEO metadata will only be computed for the layout instead of every component in your app.

### Merging i18n SEO metadata with your own

If you want to add your own meta in the layout you can easily merge the object returned by `$nuxtI18nSeo` with your own:

```js
// layouts/default.vue

export default {
head () {
const i18nSeo = this.$nuxtI18nSeo()
return {
htmlAttrs: {
myAttribute: 'My Value',
...i18nSeo.htmlAttrs
},
meta: [
{
hid: 'description',
name: 'description',
content: 'My Custom Description'
},
...i18nSeo.meta
],
link: [
{
hid: 'apple-touch-icon',
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-touch-icon.png'
},
...i18nSeo.link
]
}
}
}
```
4 changes: 4 additions & 0 deletions src/plugins/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import { nuxtI18nSeo } from './seo-head'

Vue.use(VueI18n)

Expand Down Expand Up @@ -57,6 +58,9 @@ export default async ({ app, route, store, req }) => {
app.i18n.beforeLanguageSwitch = <%= options.beforeLanguageSwitch %>
app.i18n.onLanguageSwitched = <%= options.onLanguageSwitched %>

// Inject seo function
Vue.prototype.$nuxtI18nSeo = nuxtI18nSeo

if (store && store.state.localeDomains) {
app.i18n.locales.forEach(locale => {
locale.domain = store.state.localeDomains[locale.code];
Expand Down
72 changes: 2 additions & 70 deletions src/plugins/seo.js
Original file line number Diff line number Diff line change
@@ -1,74 +1,6 @@
import Vue from 'vue'
import { nuxtI18nSeo } from './seo-head'

Vue.mixin({
head () {
const COMPONENT_OPTIONS_KEY = '<%= options.COMPONENT_OPTIONS_KEY %>'
if (
!this._hasMetaInfo ||
!this.$i18n ||
!this.$i18n.locales ||
this.$options[COMPONENT_OPTIONS_KEY] === false ||
(this.$options[COMPONENT_OPTIONS_KEY] && this.$options[COMPONENT_OPTIONS_KEY].seo === false)
) {
return {};
}
const LOCALE_CODE_KEY = '<%= options.LOCALE_CODE_KEY %>'
const LOCALE_ISO_KEY = '<%= options.LOCALE_ISO_KEY %>'
const BASE_URL = '<%= options.baseUrl %>'

// Prepare html lang attribute
const currentLocaleData = this.$i18n.locales.find(l => l[LOCALE_CODE_KEY] === this.$i18n.locale)
const htmlAttrs = {}
if (currentLocaleData && currentLocaleData[LOCALE_ISO_KEY]) {
htmlAttrs.lang = currentLocaleData[LOCALE_ISO_KEY]
}

// hreflang tags
const link = this.$i18n.locales
.map(locale => {
if (locale[LOCALE_ISO_KEY]) {
return {
hid: 'alternate-hreflang-' + locale[LOCALE_ISO_KEY],
rel: 'alternate',
href: BASE_URL + this.switchLocalePath(locale.code),
hreflang: locale[LOCALE_ISO_KEY]
}
} else {
console.warn('[<%= options.MODULE_NAME %>] Locale ISO code is required to generate alternate link')
return null
}
})
.filter(item => !!item)

// og:locale meta
const meta = []
// og:locale - current
if (currentLocaleData && currentLocaleData[LOCALE_ISO_KEY]) {
meta.push({
hid: 'og:locale',
name: 'og:locale',
property: 'og:locale',
// Replace dash with underscore as defined in spec: language_TERRITORY
content: currentLocaleData[LOCALE_ISO_KEY].replace(/-/g, '_')
})
}
// og:locale - alternate
meta.push(
...this.$i18n.locales
.filter(l => l[LOCALE_ISO_KEY] && l[LOCALE_ISO_KEY] !== currentLocaleData[LOCALE_ISO_KEY])
.map(locale => ({
hid: 'og:locale:alternate-' + locale[LOCALE_ISO_KEY],
name: 'og:locale:alternate',
property: 'og:locale:alternate',
content: locale[LOCALE_ISO_KEY].replace(/-/g, '_')
}))
);

return {
htmlAttrs,
link,
meta
}
}
head: nuxtI18nSeo
})

69 changes: 69 additions & 0 deletions src/templates/seo-head.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export const nuxtI18nSeo = function () {
const COMPONENT_OPTIONS_KEY = '<%= options.COMPONENT_OPTIONS_KEY %>'
if (
!this._hasMetaInfo ||
!this.$i18n ||
!this.$i18n.locales ||
this.$options[COMPONENT_OPTIONS_KEY] === false ||
(this.$options[COMPONENT_OPTIONS_KEY] && this.$options[COMPONENT_OPTIONS_KEY].seo === false)
) {
return {};
}
const LOCALE_CODE_KEY = '<%= options.LOCALE_CODE_KEY %>'
const LOCALE_ISO_KEY = '<%= options.LOCALE_ISO_KEY %>'
const BASE_URL = '<%= options.baseUrl %>'

// Prepare html lang attribute
const currentLocaleData = this.$i18n.locales.find(l => l[LOCALE_CODE_KEY] === this.$i18n.locale)
const htmlAttrs = {}
if (currentLocaleData && currentLocaleData[LOCALE_ISO_KEY]) {
htmlAttrs.lang = currentLocaleData[LOCALE_ISO_KEY]
}

// hreflang tags
const link = this.$i18n.locales
.map(locale => {
if (locale[LOCALE_ISO_KEY]) {
return {
hid: 'alternate-hreflang-' + locale[LOCALE_ISO_KEY],
rel: 'alternate',
href: BASE_URL + this.switchLocalePath(locale.code),
hreflang: locale[LOCALE_ISO_KEY]
}
} else {
console.warn('[<%= options.MODULE_NAME %>] Locale ISO code is required to generate alternate link')
return null
}
})
.filter(item => !!item)

// og:locale meta
const meta = []
// og:locale - current
if (currentLocaleData && currentLocaleData[LOCALE_ISO_KEY]) {
meta.push({
hid: 'og:locale',
name: 'og:locale',
property: 'og:locale',
// Replace dash with underscore as defined in spec: language_TERRITORY
content: currentLocaleData[LOCALE_ISO_KEY].replace(/-/g, '_')
})
}
// og:locale - alternate
meta.push(
...this.$i18n.locales
.filter(l => l[LOCALE_ISO_KEY] && l[LOCALE_ISO_KEY] !== currentLocaleData[LOCALE_ISO_KEY])
.map(locale => ({
hid: 'og:locale:alternate-' + locale[LOCALE_ISO_KEY],
name: 'og:locale:alternate',
property: 'og:locale:alternate',
content: locale[LOCALE_ISO_KEY].replace(/-/g, '_')
}))
);

return {
htmlAttrs,
link,
meta
}
}

0 comments on commit ce373c4

Please sign in to comment.