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