Nuxt - how to create a sitemap Google Search will like!
Table of contents
Hello!
I recently implemented Sitemap in Nuxt, including i18n (internationalization) and dynamic links. I didn't find any tutorial online about implementing a sitemap combining these options, so I hope this guide will help some other devs.
Internationalization plugin
@nuxtjs/i18n
provides functionality that enables having i18n in your app. I will assume that you already have some setup for it. That's how it looks for me:
export default defineNuxtConfig({
//..Other config sections
modules: [
//..Other modules
['@nuxtjs/i18n',{
locales: [
{ code: 'en', iso: 'en-US' },
{ code: 'sv', iso: 'sv-SE' },
// Add more locales here
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
vueI18n: './i18n.config.ts',
detectBrowserLanguage: false,
}
],
]
})
locales
specifies which locales you want to have in your sitemap.
strategy
meaning is explained in the documentation.
detectBrowserLanguage
is annoying, I recommend setting it to false
.
vueI18n
is my file with my messages, it looks approximately like this
export default defineI18nConfig(() => ({
legacy: false,
locale: 'en',
messages: {
// ADD COMA AFTER EACH LINE BELOW, OTHERWISE IT CRASHES WITH UNSPECIFIC ERROR
en: {
// NAVBAR
navbarHome: 'Home',
..., //your other messages
},
sv: {
// NAVBAR
navbarHome: 'Hem',
..., //your other messages
},
}
}))
Sitemap and defining dynamic routes
I chose to use @nuxtjs/sitemap
Part of my setup in nuxt.config.ts that focuses on sitemap module looks like this:
export default defineNuxtConfig({
//..Other config sections
modules: [
//..Other modules
['@nuxtjs/sitemap',
{
sources: ['/api/sitemap'],
xslColumns: [
{ label: 'URL', width: '50%' },
{ label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },
{ label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' },
],
}],
],
})
xslColumns
is defining how the sitemap is presented, check the documentation to learn more.
The sources
are actually where we specify the dynamic links that we want to add to our sitemap. More info in the documentation.
In the contents
constant, I receive a list of all the articles that I have in my backend. Then I proceed to extract slugs for all of them and prepare them
My file looks approximately like this, it's not perfectly optimized but should give you an idea:
export default defineSitemapEventHandler(async () => {
// get all the slugs
async function fetchContents() {
try {
const contents = await $fetch<ApiResponseContent>('link_to_api_endpoint');
// Extract the slugs.
const sitemapEntries = contents.data.map(content => {
const mainSlug = content.attributes.slug
// Some blog articles will have localizations, if not return []
const localizationEntries = content.attributes.localizations?.data.map(localization => {
return {
link: [
{
hreflang: localization.attributes.locale,
href: localization.attributes.slug
},
{
hreflang: 'x-default', // Assuming 'en' is the default locale
href: content.attributes.locale == 'en' ? mainSlug : localization.attributes.slug}
}
]
};
}) || [];
return [
{
loc: mainSlug,
_sitemap: content.attributes.locale == 'en' ? "en-US" : "sv-SE",
link: [
{
hreflang: content.attributes.locale,
href: mainSlug
},
...localizationEntries.map(entry => entry.link).flat()
]
},
];
});
return sitemapEntries;
} catch (error) {
console.error("Error fetching pages:", error);
return [];
}
}
// Call the function and handle the slugs
const slugsContents = await fetchContents()
const allSlugs = [...slugsContents]
const flattenedSlugs = allSlugs.flat();
return [
...flattenedSlugs.map(content => asSitemapUrl({
loc: content.loc,
alternatives: content.link,
_sitemap: content._sitemap
}))
]
This way we end up with a list of slugs in an asSitemapUrl format. We specify the link (loc) of every item and its alternatives. Each item WITHOUT translation should have 1 alternative (the link to the item itself), and each item WITH translation should have 3 alternatives (the item's link, its other language version link, and its default language).
Some slugs should end up in an English sitemap, some in the Swedish one. You specify in which sitemap they should be by specifying the _sitemap parameter. Make sure you use the correct name that i18n is using for your locales.
Result
If you follow my description, you should be able to see the resulting sitemaps at yourwebsitelink.com/sitemap.xml after starting your application. You can then proceed to change the look of each of the sitemaps by editing the xslColumns
parameter.
Hope this guide gave you some sense of direction on how to implement i18n and dynamic links in the sitemap in Nuxt. If you have any questions feel free to reach out to me, I'll be happy to help!
Cheers!
Bart
Subscribe to my newsletter
Read articles from Bart Martin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by