Nextjs have done some changes since last post that I wrote. Since then was getStaticProps, getStaticPaths and getServerSidedProps introduced(You can read more about that here: https://nextjs.org/blog/next-9-4) One way to generate a sitemap with nextjs after the update on SSG and SSR with a dynamic output based on the pages the application have is to create an API route that fetches data and return it as XML which could be used to read what pages you have on your application.

Create the code that generates the XML feed:

The way I generated my sitemap is with a package named “sitemap” which is an awesome package to generate a sitemap with. The generation of the sitemap looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// import functions from the package
import { SitemapStream, streamToPromise } from "sitemap";

// Fetch data from a source which will be used to render the sitemap.
const { posts } = await graphlqlFetch(`
    query getSitemapData {
        projects: allWorks {
            slug {
                current
            }
            publishedAt
        }
    }
`);

// Create the a stream to write to with a hostname which will be used for all links
// Your are able to add more settings to the stream. I recommend to look a the npm package for more information.
const smStream = new SitemapStream({
    hostname: "https://priver.dev",
});

// Add frontpage
smStream.write({
    url: "/",
});

// Add a static url to ex: about page
smStream.write({
    url: "/about",
});

// add all dynamic url to the sitemap which is fetched from a source.
posts.forEach((element) => {
    smStream.write({
        url: `/${element.slug.current}`,
        lastmod: element.publishedAt,
    });
});

// tell sitemap that there is nothing more to add to the sitemap
smStream.end();


// generate a sitemap and add the XML feed to a url which will be used later on.
const sitemap = await streamToPromise(smStream).then((sm) => sm.toString());

But this is only the code that generates the sitemap. They sitemap const will later on be used to send data to the user or an service which want to read the sitemap/xml feed. ##Send the sitemap to the user:

Nextjs have a build in way to return data to a user or service when an api route is generated. Which means we dont need any external packages like express. They way I returned data to the user or service when using an api route is this code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
export default async (req, res) => {
    // here is the generation of the sitemap happening
    
    // tell the output that we will output XML
    res.setHeader("Content-Type", "text/xml");
    // write the generate sitemap to the output
    res.write(sitemap);
    // end and send the data to the user or service.
    res.end();
};

Now the user will have a sitemap that is dynamic generated and outputed in a way that a service like Goole Search Engine is able to read it.

Want to use domain.ltd/sitemap.xml as url for your sitemap?

To use domain.ltd/sitemap.xml in this solution do you basic add a rewrite from /sitemap.xml to /api/sitemap in next.config.js. More about rewrites here: https://nextjs.org/docs/api-reference/next.config.js/rewrites Like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module.exports = {
    async rewrites() {
        return [
            {
                source: "/sitemap.xml",
                destination: "/api/sitemap",
            },
        ];
    },
};

The full code:

This is the whole code I used to generate a dynamic sitemap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// import functions from the package
import { SitemapStream, streamToPromise } from "sitemap";

// A custom function I use to fetch data from a backend. I will keep the import to make it more clear why "graphlqlFetch" is used in the code
import graphlqlFetch from "lib/apollo"

export default async (req, res) => {
// Fetch data from a source which will be used to render the sitemap.
const { posts } = await graphlqlFetch(`
    query getSitemapData {
        projects: allWorks {
            slug {
                current
            }
            publishedAt
        }
    }
`);

// Create the a stream to write to with a hostname which will be used for all links
// Your are able to add more settings to the stream. I recommend to look a the npm package for more information.
const smStream = new SitemapStream({
    hostname: "https://priver.dev",
});

// Add frontpage
smStream.write({
    url: "/",
});

// Add a static url to ex: about page
smStream.write({
    url: "/about",
});

// add all dynamic url to the sitemap which is fetched from a source.
posts.forEach((element) => {
    smStream.write({
        url: `/${element.slug.current}`,
        lastmod: element.publishedAt,
    });
});

// tell sitemap that there is nothing more to add to the sitemap
smStream.end();

// generate a sitemap and add the XML feed to a url which will be used later on.
const sitemap = await streamToPromise(smStream).then((sm) => sm.toString());
// here is the generation of the sitemap happening

// tell the output that we will output XML
res.setHeader("Content-Type", "text/xml");
// write the generate sitemap to the output
res.write(sitemap);
// end and send the data to the user or service.
res.end();
};

GitHub gist with the same code: GitHub Gist

Hope this help you to generate a dynamic sitemap for your Nextjs application