diff --git a/packages/@vuepress/core/lib/client/index.ssr.html b/packages/@vuepress/core/lib/client/index.ssr.html index 351c008efe..5888234d40 100644 --- a/packages/@vuepress/core/lib/client/index.ssr.html +++ b/packages/@vuepress/core/lib/client/index.ssr.html @@ -7,6 +7,7 @@ {{{ userHeadTags }}} {{{ pageMeta }}} + {{{ canonicalLink }}} {{{ renderResourceHints() }}} {{{ renderStyles() }}} diff --git a/packages/@vuepress/core/lib/client/root-mixins/updateMeta.js b/packages/@vuepress/core/lib/client/root-mixins/updateMeta.js index 485f544d38..c2265702f2 100644 --- a/packages/@vuepress/core/lib/client/root-mixins/updateMeta.js +++ b/packages/@vuepress/core/lib/client/root-mixins/updateMeta.js @@ -13,6 +13,7 @@ export default { this.$ssrContext.title = this.$title this.$ssrContext.lang = this.$lang this.$ssrContext.pageMeta = renderPageMeta(mergedMetaItems) + this.$ssrContext.canonicalLink = renderCanonicalLink(this.$canonicalUrl) } }, // Other life cycles will only be called at client @@ -22,6 +23,7 @@ export default { // update title / meta tags this.updateMeta() + this.updateCanonicalLink() }, methods: { @@ -39,18 +41,45 @@ export default { // description needs special attention as it has too many entries return unionBy([{ name: 'description', content: this.$description }], pageMeta, this.siteMeta, metaIdentifier) + }, + + updateCanonicalLink () { + removeCanonicalLink() + + if (!this.$canonicalUrl) { + return + } + + document.head.insertAdjacentHTML('beforeend', renderCanonicalLink(this.$canonicalUrl)) } }, watch: { $page () { this.updateMeta() + this.updateCanonicalLink() } }, beforeDestroy () { updateMetaTags(null, this.currentMetaTags) + removeCanonicalLink() + } +} + +function removeCanonicalLink () { + const canonicalEl = document.querySelector("link[rel='canonical']") + + if (canonicalEl) { + canonicalEl.remove() + } +} + +function renderCanonicalLink (link = '') { + if (!link) { + return '' } + return `` } /** diff --git a/packages/@vuepress/core/lib/node/ClientComputedMixin.js b/packages/@vuepress/core/lib/node/ClientComputedMixin.js index dbe8a3f7a5..2ea38bbddc 100644 --- a/packages/@vuepress/core/lib/node/ClientComputedMixin.js +++ b/packages/@vuepress/core/lib/node/ClientComputedMixin.js @@ -65,6 +65,16 @@ module.exports = siteData => { return this.$localeConfig.title || this.$site.title || '' } + get $canonicalUrl () { + const { canonical } = this.$page.frontmatter + + if (typeof canonical === 'string') { + return canonical + } + + return false + } + get $title () { const page = this.$page const { metaTitle } = this.$page.frontmatter diff --git a/packages/docs/docs/guide/frontmatter.md b/packages/docs/docs/guide/frontmatter.md index 39a0d2d76b..5fa3718476 100644 --- a/packages/docs/docs/guide/frontmatter.md +++ b/packages/docs/docs/guide/frontmatter.md @@ -110,6 +110,13 @@ meta: --- ``` +### canonicalUrl + +- Type: `string` +- Default: `undefined` + +Set the canonical URL for the current page. + ## Predefined Variables Powered By Default Theme ### navbar