DEV Community

Andrey Gaisinskii
Andrey Gaisinskii

Posted on

Sitemap for dynamic routes in NuxtJS

Motivation

As for the date of writing this post it is impossible to dynamically generate sitemap.xml in NuxtJS for dynamic routes using @nuxtjs/sitemap.

As the documentation for the module says, you have manually type all your dynamic routes into the routes array of sitemap object in nuxt.config.js.

// nuxt.config.js
{
  sitemap: {
    hostname: 'https://example.com',
    gzip: true,
    exclude: [
      '/secret',
      '/admin/**'
    ],
    routes: [
      'dynamic-route-one',
      'dynamic-route-two',
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

There is also another way, where you have to create a script that will make all the API calls to your backend server then will generate an array of routes that the sitemap object in the nuxt.config.js will then consume. More on that you can search for yourself or take a look at this article.

But I was very curious if there is another way, because it can be a pain the in the ass to type in all the dynamic routes manually or to make a lot of the API calls.

Disclaimer

After reading this post you might think that this way of generating
sitemap for dynamic routes is a bit hacky. And you are absolutely true. There is only one official way of doing it. Use at your own risk.

Watered down theory

Since Nuxt v2.13 a new option in nuxt.config.js is available:

// nuxt.config.js
export default {
  generate: {
    crawler: false // default - true
  }
}
Enter fullscreen mode Exit fullscreen mode

A quote from the documentation:

As of Nuxt >= v2.13 Nuxt.js comes with a crawler installed that will crawl your relative links and generate your dynamic links based on these links. If you want to disable this feature you can set the value to false.

I have created a repository which has both dynamic and static routes and if we run npm run generate in the terminal we can see our dynamic routes being generated as individual html files:

Alt Text

But also you can see that the path to the dynamic route being printed out into the terminal, eg. - Generated route "/users/10". So if it is printed out, maybe it is stored somewhere and we can obtain it or we can capture it while it is being printed out and we can store it.

Coding part

Lets quickly install @nuxtjs/sitemap module by typing in the terminal:

npm install @nuxtjs/sitemap
or
yarn add @nuxtjs/sitemap

then add it to nuxt.config.js like this:

// nuxt.config.js
modules: [
    // your other modules
    '@nuxtjs/sitemap'
  ],
Enter fullscreen mode Exit fullscreen mode

and configure it like this:

// nuxt.config.js
sitemap: {
    hostname: 'https://my-host.com',
    gzip: true,
    exclude: [
      '/exclude-one',
      '/exclude-two'
    ],
    defaults: {
      changefreq: 'daily',
      priority: 1,
      lastmod: new Date()
    }
  },
Enter fullscreen mode Exit fullscreen mode

By running npm run generate again the in terminal we can ensure that dynamic routes are being generated but not being added to the sitemap.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml">
   <url>
      <loc>https://my-host.com/albums</loc>
      <lastmod>2020-10-13T11:19:36.882Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/todos</loc>
      <lastmod>2020-10-13T11:19:36.882Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users</loc>
      <lastmod>2020-10-13T11:19:36.882Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/</loc>
      <lastmod>2020-10-13T11:19:36.882Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
</urlset>
Enter fullscreen mode Exit fullscreen mode

In order to obtain those generated routes paths we will use NuxtJS hooks which can be used in the NuxtJS modules.

To create a module just make a modules folder in your project directory with a file inside called generator.ts

Note: I am using TypeScript here, if you are using vanilla JavaScript it should be generator.js

Register your generator.ts in the nuxt.config.js

// nuxt.config.js
 buildModules: [
    // other build modules
    '@/modules/generator'
  ],
Enter fullscreen mode Exit fullscreen mode

Inside the generator.ts paste the following code and let's reflect on it a little bit.

import { Module } from '@nuxt/types'

const generator: Module = function () {
  this.nuxt.hook('generate:done', async (context: any) => {
    const routesToExclude: string[] =
    process.env.NUXT_ENV_EXCLUDE_ROUTES
      ? process.env.NUXT_ENV_EXCLUDE_ROUTES.split(',') : []
    const allRoutes: string[] = await Array.from(context.generatedRoutes)
    const routes: string[] = await allRoutes.filter((route: string) => !routesToExclude.includes(route))
    this.nuxt.options.sitemap.routes = await [...routes]
  })
}

export default generator
Enter fullscreen mode Exit fullscreen mode

1) We have defined a generator function that is exported and will be injected into NuxtJS.
2) We have subscribed to a generate:done hook and upon hook completion the code inside the function will be executed.
3) If you take a look here you will see that a certain context will be returned by the hook. If you console.log that context inside our module, you will see a generatedRoutes Set
4) Inside routesToExclude I use a ternary operator to ensure that I do have some data in my NUXT_ENV_EXCLUDE_ROUTES environment variable:

// .env
NUXT_ENV_EXCLUDE_ROUTES = '/exclude-one,/exclude-two'
Enter fullscreen mode Exit fullscreen mode

Then I divide my string into substrings to become an array of strings by using .split method.
5) Inside allRoutes I transform Set into an Array by using Array.from method.
6) I filter out all the routes I want to exclude by using filter method in routes
7) And lastly i spread my filtered routes into the routes property of the sitemap object: this.nuxt.options.sitemap.routes = await [...routes]

Now if you run npm run generate again you will see dynamic routes in the sitemap.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml">
   <url>
      <loc>https://my-host.com/albums</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/todos</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/1</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/2</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/3</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/4</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/5</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/6</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/7</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/8</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/9</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
   <url>
      <loc>https://my-host.com/users/10</loc>
      <lastmod>2020-10-13T12:09:44.775Z</lastmod>
      <changefreq>daily</changefreq>
      <priority>1.0</priority>
   </url>
</urlset>
Enter fullscreen mode Exit fullscreen mode

For those who are not familiar with TypeScript

Leave a comment below or DM me, i will try to help you out.

Links

Live preview can be found here - https://andynoir.github.io/article-nuxt-dynamic-sitemap/
Sitemap here https://andynoir.github.io/article-nuxt-dynamic-sitemap/sitemap.xml
GitHub repo here - https://github.com/andynoir/article-nuxt-dynamic-sitemap

Top comments (24)

Collapse
 
djmenkveld profile image
Dirk Menkveld • Edited

Great article, thanks. Question, is there way to set the hostname in nuxt.config.js dynamically running with server.js? Example, this would be helpful in case of multiple domains my-host.com and my-host-b.com are pointing to the same nuxt app.

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Hi!

Not quite sure what do you mean, but you can access nuxt config inside your module by this.nuxt.options.sitemap.hostname. Also you can use different envs.

Collapse
 
djmenkveld profile image
Dirk Menkveld

Hey men, thanks for replying.

Well, the host needs to be dynamic, where I've different data connected to each host.

Example domains:
tourzomer.com
fantaciclo.it
grandtourfantasy.com

All domains are for different purposes and have dynamics path for teams and others. So, in the nuxt config or sitemap config I call call for example teams per domain from my backend. But, in order to do i need to know which domain it is. So, i set a client-host to my call, but for some reason i do not know which domain the app is.

Does this make sense?

Thread Thread
 
gaisinskii profile image
Andrey Gaisinskii

Still don't get it:)

But maybe you can add a custom header to your API calls. And each of your websites will have a custom header, for e.g. x-platform: tourzomer, x-platform: fantaciclo and so on.

Thread Thread
 
djmenkveld profile image
Dirk Menkveld

Hi, yeah that's the solution, but the problem is that I can't access the hostname tourzomer or fantaciclo in nuxt config or the utils helper I have to get the routes.

Thread Thread
 
mehattabi profile image
MeHattabi

Have you found a solution? I'm in the same situatien and going crazy here..

Collapse
 
takburgerap profile image
takburger

I wondered why the generate was able to display everything I needed for my sitemap and the sitemap module couldn't. Seems counterintuitive.
That's a great tutorial.
Could you add the bit of typescript but in JS ? I'm too poorly skilled to unpack your TS and try to put it in my project. I also tried to add TS to my nuxt project but it throws an error and I'm not too kin to add 3 packages just for this.

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Regarding your question about sitemap module not generating dynamic routes: github.com/nuxt-community/sitemap-... i think that maintainers of the module filter out the dynamic routes.

And here is none-typescript version of the project - github.com/andynoir/artcile-nuxt-d...

Collapse
 
takburgerap profile image
takburger

Thank you so much

Collapse
 
sushiljic profile image
Sushil G.C.

Hi Andrey

Great Post. Loved it.
I was wondering what could be setup for SSR type webapp.
For example,
I usually deploy nuxt SSR webapp using npm run build in production and I would be very helpful If can use above method.

I tried looking for hook with build:done something but couldnot get any in Link.
nuxtlink

Also Since you have generate all dynamic 10 pages of users but for realtime siite,
We will have dynamic new content that are being generated after generate nuxt command so what can be the best solution?

❤️ from Nepal

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Sorry for the late reply, but I think there is no best solution in this case.

In my case I am using SSG and I don't have such problem because my routes and sitemap files are FIXED in some way or another, if someone want to add a new page or other content the entire app has to be rebuild hence the sitemap will be rebuilt too.

In your case the content is always dynamic, but the sitemap will be generated only after deploy if I understand correctly. I think it is better to address this problem to the maintainer of the library, because I remember that he was going to add such feature.

Collapse
 
pedroslvieira profile image
Pedro Soares

Hey Sushil! Have you found a solution for this problem? I have to solve the same issue and would like to understand how to serve the sitemap in production when new content is added to the website.

cheers!

Collapse
 
scoch5 profile image
Daniele Scotti

This article is very useful, thank you very much! ✌️
I believe that the best way to exclude the routes is not to use the env variable, but read the routes to exclude in the configuration of the sitemap module from this.nuxt.options.sitemap.exclude

Collapse
 
luisjoserivera profile image
Luis Jose Rivera • Edited

Or simply avoid the whole module and TS thing by adding this to nuxt.config.js

hooks: {
generate: {
async done(context) {
const allRoutes = await Array.from(context.generatedRoutes);
context.options.sitemap.routes = await [...allRoutes];
},
},
},

Collapse
 
filippolcr profile image
Wonderman

Ehi Andrey, great point here. Thanks

Do you know if it's possible to get head meta tags after nuxt generate is done?

I want to exclude some routes from sitemap it a certain meta tag is properly generated.

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Sorry for the late reply, I'm not quite sure what do you mean. Do you want to prevent some routes to be included into sitemap if your meta tag meets some criteria ? If yes, unfortunately I don't have an answer for you, it would be just a brute force console logging for me to answer your question. Take a look here, it might help you.

Collapse
 
simonholdorf profile image
Simon Holdorf

This helped me a lot, thanks!

Collapse
 
bacodekiller profile image
Quang Hiep • Edited

can use that for website has pagination? tks

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Can't quite understand what do you mean. Sitemap is not related to pagination by any means.

Collapse
 
jakobbaranowski profile image
JakobBaranowski

Hi there,

I am having issues because i can see the dynamic routes being generated but it doesn't add them to the sitemap.xml. I am having the exact same setup as you have. Any ideas?

Collapse
 
pastorsi profile image
Simon Measures

I'm trying to implement this but having a problem. When I run my Nuxt project in dev ($ npm run dev), I get:
Error: Cannot find module '@/modules/generator'
Do have any suggestions?

Collapse
 
gaisinskii profile image
Andrey Gaisinskii

Hi!

Which version of Nuxt are you using ? A quote from the docs:

If you are going to offer using buildModules please mention that this feature is only available since Nuxt v2.9. Older users should upgrade Nuxt or use the modules section.

Also check out for typos and file extensions.

Collapse
 
imlwi profile image
Louis Dickinson

Awesome Tutorial 👍👍👍👍👍👍

Collapse
 
lecodeurnormand profile image
Le codeur Normand

Thanks it works !