Sitemap: What is and how to generate it for a Next.js App

March 25, 2021 •

☕️

3 min read

Trying to improve my personal Site SEO I end up with the need to generate dynamically a sitemap, but first of all...

What is a Sitemap?

A sitemap is a blueprint of your website that help search engines find, crawl and index all of your website's content. Yep, I saved you a google search 😉

The sitemap is located on /sitemap.xml and looks like

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://santosnicolas.com/404</loc>
  </url>
  <url>
      <loc>https://santosnicolas.com/blog</loc>
  </url>
  <url>
    <loc>https://santosnicolas.com</loc>
  </url>
  <url>
    <loc>https://santosnicolas.com/notes</loc>
  </url>
  <url>
    <loc>https://santosnicolas.com/notes/whatever-post-title</loc>
  </url>
</urlset>

How I generate the sitemap on Next.js

We basically need to add

<url>
    <loc>${routePage}</loc>
</url>

For every page that we had on our application.

Because of this we need to get all our page routes, or at least the ones that are public. This is an easy task with globby, this lib allow us to get the name of the files based on regex URL on our folder structure.

const globby = require("globby")

;(async () => {
  // Take all the pages except for _app.tsx and _document.tsx
  const pagesPaths = await globby(["pages/*.tsx", "!pages/_*.tsx"])

  console.log(pagesPaths)
  //=> ['index.tsx', 'blog.tsx', 'notes.tsx']
})()

With fs and prettier we can format and write our generated file(sitemap.xml) to located in the public folder.

// generateSitemap.js
const fs = require("fs")
const globby = require("globby")
const prettier = require("prettier")

;(async () => {
  console.info("Generating Sitemap 🗺")
  const prettierConfig = await prettier.resolveConfig("./.prettierrc.js")
  const pages = await globby(["pages/*.tsx", "!pages/_*.tsx"])

  const sitemap = `
        <?xml version="1.0" encoding="UTF-8"?>
        <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
            ${pages
              .map((page) => {
                const path = page
                  .replace("pages/", "/")
                  .replace("public/", "/")
                  .replace(".tsx", "")
                  .replace("/index.xml", "")
                const route = path === "/index" ? "" : path
                return `
                        <url>
                            <loc>${`${siteMetadata.siteUrl}${route}`}</loc>
                        </url>
                    `
              })
              .join("")}
        </urlset>
    `

  const formatted = prettier.format(sitemap, {
    ...prettierConfig,
    parser: "html",
  })

  // eslint-disable-next-line no-sync
  fs.writeFileSync("public/sitemap.xml", formatted)
  console.info("Success generation of sitemap 🎉")
})()

Finally, we need to run this script every time that next js builds the application

// next.config.js
module.exports = {
  webpack(config, { dev, isServer }) {
    // Other next.js configuration...

    if (isServer) {
      require("./scriptsPath/generateSitemap")
    }

    return config
  },
}

And voila 🎉 our sitemap is generated every time we build our application.

Final Notes

This example doesn't consider the case in which our paths are generated dynamically, like for example if we have pages/blog/[slug].tsx, but I think it will be easy to add that part based on the initial script. I'm gonna leave in this Github gist in case you need a boost 😉