Generated blog posts are: "Duplicate, submitted URL not selected as canonical" according to Google Search Console
See original GitHub issueVersion
@nuxt/content: v1.10.0 nuxt: v2.14.4
Reproduction Link
Steps to reproduce
Not really sure there’s any steps to reproduce other than ensuring that a Nuxt JS website is in universal
mode and is a static
site.
Needless to say, here’s my nuxt.config.js file:
export default {
/*
** Nuxt rendering mode
** See https://nuxtjs.org/api/configuration-mode
*/
mode: 'universal',
/*
** Nuxt target
** See https://nuxtjs.org/api/configuration-target
*/
target: 'static',
/*
** Env variables
*/
env: {
BASE_URL: process.env.BASE_URL || "https://domain-monitor.io",
API_URL: process.env.API_URL || "http://127.0.0.1:8000",
ONESIGNAL_PUSH_APP_ID: process.env.ONESIGNAL_PUSH_APP_ID || "",
ONESIGNAL_SAFARI_WEB_ID: process.env.ONESIGNAL_SAFARI_WEB_ID || "",
GA_ID: process.env.GA_ID || ""
},
/*
** Headers of the page
** See https://nuxtjs.org/api/configuration-head
*/
head: {
title: 'Domain Monitor',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'og:title', name: 'og:title', content: 'Domain Monitor' },
{ hid: 'description', name: 'description', content: 'Keep track of your expiring domains today for FREE with our FREE domain monitoring product.' },
{ hid: 'og:description', name: 'og:description', content: 'Keep track of your expiring domains today for FREE with our FREE domain monitoring product.' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
]
},
/*
** Global CSS
*/
css: [
'@/assets/scss/domain-monitor.scss'
],
/*
** Bootstrap Vue
*/
bootstrapVue: {
bootstrapCSS: false,
bootstrapVueCSS: false
},
/*
** Plugins to load before mounting the App
** https://nuxtjs.org/guide/plugins
*/
plugins: [
{ mode: 'client', src: '@/plugins/bootstrap-vue-icons' },
{ mode: 'client', src: '@/plugins/vue-axios' },
{ mode: 'client', src: '@/plugins/vee-validate' },
{ mode: 'client', src: '@/plugins/vue-moment' },
{ mode: 'client', src: '@/plugins/content-images' },
{ mode: 'client', src: '@/plugins/content-videos' }
],
/*
** Auto import components
** See https://nuxtjs.org/api/configuration-components
*/
components: true,
/*
** Nuxt.js dev-modules
*/
buildModules: [
['@nuxtjs/google-analytics', {
id: process.env.GA_ID
}]
],
/*
** Nuxt.js modules
*/
modules: [
'bootstrap-vue/nuxt',
'@nuxtjs/axios',
'@nuxtjs/auth',
'@nuxtjs/onesignal',
'@nuxtjs/pwa',
'@nuxt/content',
'@nuxtjs/sitemap',
['@nuxtjs/component-cache', { maxAge: 1000 * 60 * 5 }] // 5 minutes
],
/*
** Auth config
*/
auth: {
redirect: {
login: '/account/login',
logout: '/account/login',
callback: '/account/login',
home: '/account/domains'
},
strategies: {
local: {
login: { url: 'auth/login', method: 'post', propertyName: 'token' },
logout: { url: 'account/logout', method: 'post' },
user: { url: 'auth/user', method: 'get', propertyName: 'user' }
}
}
},
/*
** One Signal
*/
oneSignal: {
init: {
appId: process.env.ONESIGNAL_PUSH_APP_ID,
safari_web_id: process.env.ONESIGNAL_SAFARI_WEB_ID,
allowLocalhostAsSecureOrigin: true,
welcomeNotification: {
disable: true
}
}
},
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {
baseURL: process.env.API_URL
},
/*
** Build configuration
** See https://nuxtjs.org/api/configuration-build/
*/
build: {
extractCSS: true,
extend (config, ctx) {
const vueLoader = config.module.rules.find((rule) => rule.loader === 'vue-loader')
vueLoader.options.transformAssetUrls = {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href',
'b-img': 'src',
'b-img-lazy': ['src', 'blank-src'],
'b-card': 'img-src',
'b-card-img': 'img-src',
'b-card-img-lazy': ['src', 'blank-src'],
'b-carousel-slide': 'img-src',
'b-embed': 'src'
}
}
},
/*
** Sitemap configuration
** See https://www.npmjs.com/package/@nuxtjs/sitemap#setup
*/
sitemap: {
hostname: process.env.BASE_URL, // https://www.yoursite.com
exclude: [
'/account/recovery',
'/account/reset',
'/account/profile',
'/account/domains/add',
'/account/domains',
'/account/monitors/add',
'/account/monitors',
'/help/account/introduction',
'/help/monitors/introduction',
'/help/domains/introduction'
],
routes() {
return getRoutes();
},
}
}
I’ll also attach the template responsible for building up a blog post page (that I’ve generated) inside of pages/blog/_slug.vue
<template>
<div>
<section class="py-3 bg-light position-relative">
<b-container>
<b-row>
<b-col cols="12" lg="8">
<b-card no-body class="card--post">
<div class="bg-primary card-img-top" v-if="post.image">
<b-card-img :src="require(`~/assets/images/${post.image ? post.image : 'content/blog/default.svg'}`)" :alt="post.alt ? post.alt : 'Another Domain Monitor blog post just for you!'" top></b-card-img>
</div>
<b-card-body class="p-3 p-md-4">
<b-row v-if="post.categories">
<b-col>
<h6 class="mb-n1">
<b-badge variant="light" v-for="(category, index) in post.categories" :key="index" class="text-lowercase mb-1 mr-1 p-1 p-md-2"># {{ category }}</b-badge>
</h6>
</b-col>
</b-row>
<b-row>
<b-col>
<nuxt-content :document="post" class="content content--post" />
</b-col>
</b-row>
</b-card-body>
</b-card>
</b-col>
<b-col cols="12" lg="4">
<ContentSearch class="mt-3 mt-lg-0" />
<b-card no-body v-if="blogs" class="mt-3">
<b-card-header class="bg-white">
<b-row>
<b-col>
<h3 class="h5 font-weight-bold mb-0">More posts</h3>
</b-col>
</b-row>
</b-card-header>
<b-list-group flush>
<b-list-group-item v-for="(blog, index) in blogs" :key="index" :to="`/blog/${blog.slug}`" class="py-2 px-3">
<b-row>
<b-col cols="8">
<h6 class="mb-0 font-weight-bolder text-dark">{{ blog.title }}</h6>
</b-col>
<b-col class="d-flex align-items-center text-right">
<div class="w-100">
<b-button pill size="sm" type="button" variant="light" class="btn-action text-center">
<span class="material-icons md-18 d-flex align-items-center justify-content-around">arrow_forward</span>
</b-button>
</div>
</b-col>
</b-row>
</b-list-group-item>
</b-list-group>
</b-card>
<b-card no-body class="mt-3">
<b-card-header class="bg-white">
<b-row>
<b-col>
<h3 class="h5 font-weight-bold mb-0">All posts</h3>
</b-col>
</b-row>
</b-card-header>
<b-list-group flush>
<b-list-group-item to="/blog" class="py-2 px-3">
<b-row>
<b-col cols="8">
<h6 class="mb-0 font-weight-bolder text-dark">Read more of our great product news and updates</h6>
</b-col>
<b-col class="d-flex align-items-center text-right">
<div class="w-100">
<b-button pill size="sm" type="button" variant="light" class="btn-action text-center">
<span class="material-icons md-18 d-flex align-items-center justify-content-around">arrow_forward</span>
</b-button>
</div>
</b-col>
</b-row>
</b-list-group-item>
</b-list-group>
</b-card>
</b-col>
</b-row>
</b-container>
</section>
</div>
</template>
<script>
export default {
layout: 'blog',
head () {
return {
title: `${this.post.title} - Domain Monitor Blog`,
meta: [
{ hid: 'og:title', name: 'og:title', content: `${this.post.title} - Domain Monitor Blog` },
{ hid: 'description', name: 'description', content: this.post.description },
{ hid: 'og:description', name: 'og:description', content: this.post.description }
]
}
},
async asyncData ({ $content, params }) {
const post = await $content('blog', params.slug)
.fetch()
const blogs = await $content('blog')
.sortBy('createdAt', 'desc')
.limit(5)
.fetch()
const [prev, next] = await $content('blog')
.only(['title', 'slug'])
.sortBy('createdAt', 'desc')
.surround(params.slug)
.fetch()
return {
post,
prev,
next,
blogs
}
}
}
</script>
What is Expected?
I would’ve thought that when building a Nuxt JS website using npm run build && npm run generate
that there wouldn’t be any duplicated URLs (supposedly indicated by search console).
What is actually happening?
Recently, I’ve been publishing content on a daily basis to my Nuxt JS website. I have content written within a /content/blog/
directory which is then built into pages when running the npm run build && npm run generate
command. They’re also then visible within the sitemap.xml
file.
I run these commands upon every deployment (after a new piece of content has been added) and then clear the cache in CloudFlare.
It’s been a few weeks now, and some of the content has been indexed just fine by Google, but for some reason, the vast majority of my blog post pages are “excluded” from search console because of:
Duplicate, submitted URL not selected as canonical
(see attached screenshot)
Now, I’m not sure whether this is an issue related to:
- My configuration (although I doubt it because some pages have been indexed just fine)
- The Content Module, since that’s where all of my content is written and I’m sure the Content Module plays some role in generating static files and has some code built in terms of what’s injected into each page.
- Nuxt JS itself, but again, I don’t think so because my other pages (which aren’t blog related are just fine)
- The Sitemap Module, but I wouldn’t want to duplicate this Github issue twice for something that could be more likely related to the Content Module
In terms of suggestions, supposedly there are two ways to resolve this from my research, both of which don’t seem right…
- Set a
noindex
meta tag on the pages which are showing this exclusion notice. - Remove the page and redirect it to a similar page
Now, I’m trying to figure out more about why I might be getting this error, and if there’s some config or something I’ve missed, and what might be causing my exclusion errors since I can’t find anything in Search Console suggesting the root cause…
My primary concern here is that the number of pages (all of my content pages) continue to rise and have a negative effect on my SEO.
Issue Analytics
- State:
- Created 3 years ago
- Comments:14 (5 by maintainers)
Top GitHub Comments
@davydnorris That’s interesting, yet I thought that there might be something up. After implementing what I said I would, I’ve actually seen that Google has brought back these pages, and so they’re not excluded any more!
Might be worth reacting to this comment for anyone else experience a similar issue, closing out this issue now then! 😃
Thank you for your nice feedback @sts-ryan-holton