Generating a sitemap for a Next.js application

Tags:
  • typescript
  • next.js
  • node.js
  • sitemap
  • seo

Introduction

A sitemap is a file that contains a list of URLs on a website. It helps search engines to discover and index the content of a website. It's supposed to be in XML format and it should be placed in the root directory of the website. In this very short blog post, we will learn how to generate a sitemap for a Next.js application using a custom type-safe script.

Prerequisites

  • Obviously you need a Next.js application.
  • Basic knowledge of TypeScript.
  • Basic knowledge of Node.js.

Looking into the build output of this website

If you don't already know, this website is built using Next.js and more specifically with app router. When you build a Next.js application, you can see a list of routes that are generated.

For this website, the output looks like this:

Route (app)                                                                        Size     First Load JS
┌ ○ /                                                                              4.57 kB        95.8 kB
├ ○ /_not-found                                                                    0 B                0 B
├ ○ /about                                                                         191 B          91.4 kB
├ ○ /articles                                                                      1.79 kB          93 kB
├ ○ /articles/2017/executables-with-node-js                                        175 B          84.6 kB
├ ○ /articles/2017/supercharging-collaborative-api-development-with-ngrok          175 B          84.6 kB
├ ○ /articles/2018/frontend-gems                                                   175 B          84.6 kB
├ ○ /articles/2018/mass-png-to-jpg-on-windows                                      175 B          84.6 kB
├ ○ /articles/2018/node-js-advanced-snippets                                       175 B          84.6 kB
├ ○ /articles/2019/getting-started-with-webrtc                                     175 B          84.6 kB
├ ○ /articles/2019/picking-gatsby-as-a-static-site-generator                       175 B          84.6 kB
├ ○ /articles/2020/automated-doc-deployments-using-kotlin-and-netlify              175 B          84.6 kB
├ ○ /articles/2020/using-the-factory-design-pattern-with-kotlin                    175 B          84.6 kB
├ ○ /articles/2023/how-to-install-docker-using-ansible-on-ubuntu-22-04             849 B          85.3 kB
├ ○ /articles/2024/10-tailwind-classes-i-wish-i-found-earlier                      175 B          84.6 kB
├ ○ /articles/2024/10-tailwind-classes-i-wish-i-found-earlier/opengraph-image.png  0 B                0 B
├ ○ /articles/2024/a-comprehensive-guide-to-cloudflare-r2                          175 B          84.6 kB
├ ○ /articles/2024/a-comprehensive-guide-to-cloudflare-r2/opengraph-image.jpg      0 B                0 B
├ ○ /articles/2024/a-new-javascript-registry                                       175 B          84.6 kB
├ ○ /articles/2024/a-new-javascript-registry/opengraph-image.jpg                   0 B                0 B
├ ○ /articles/2024/introducing-a-new-consultation-booking-feature                  192 B          91.4 kB
├ ○ /articles/2024/replacing-dependabot-with-ncu                                   175 B          84.6 kB
├ ○ /articles/2024/replacing-dependabot-with-ncu/opengraph-image.jpg               0 B                0 B
├ ○ /articles/2024/understanding-monorepos                                         175 B          84.6 kB
├ ○ /articles/2024/understanding-monorepos/opengraph-image.jpg                     0 B                0 B
├ ○ /opengraph-image.png                                                           0 B                0 B
├ ○ /projects                                                                      192 B          91.4 kB
├ ○ /sitemap.xml                                                                   0 B                0 B
├ ○ /thank-you                                                                     175 B          84.6 kB
├ ○ /uses                                                                          192 B          91.4 kB
└ ○ /work-with-me                                                                  192 B          91.4 kB
+ First Load JS shared by all                                                      84.4 kB
  ├ chunks/69-8f09a332f747eb9e.js                                                  29.1 kB
  ├ chunks/fd9d1056-880132a998785323.js                                            53.4 kB
  └ other shared chunks (total)                                                    1.89 kB

Quite a long list, and it's only getting bigger and bigger as I write more articles or add more pages. The good thing is that we can use this list to generate a sitemap and help search engines to discover the content of this website as it grows.

Generating a sitemap

We need a script (I prefer TypeScript) that will generate a sitemap based on the routes that are generated during the build process. The file name is sitemap.ts and it will be placed in src/app directory. Basically this is a naming convention that Next.js uses to generate the end result so please make sure to follow the naming convention as well as the directory placement.

The script will look like this:

import path from 'path';
import fs from 'fs';

const extensions = ['tsx', 'mdx'];
const baseUrl = 'https://jimfilippou.com';
const baseDir = 'src/app';

function getRoutesFromDir(fullPath: string, prefix = ''): string[] {
  const entries = fs.readdirSync(fullPath, { withFileTypes: true });
  return entries.reduce((routes, entry) => {
    if (entry.isDirectory()) {
      const newPrefix = `${prefix}/${entry.name}`;
      if (extensions.some((ext) => fs.existsSync(path.join(fullPath, entry.name, `page.${ext}`)))) {
        routes.push(newPrefix);
      }
      // Recursively get routes from subdirectories
      const subDir = path.join(fullPath, entry.name);
      return routes.concat(getRoutesFromDir(subDir, newPrefix));
    }
    return routes;
  }, [] as string[]);
}

function getRoutes() {
  const fullPath = path.join(process.cwd(), baseDir);
  const routes = getRoutesFromDir(fullPath);
  return routes.map((route) => {
    return {
      url: `${baseUrl}${route}`,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1.0,
    };
  });
}

export default function sitemap() {
  return getRoutes();
}

Now what?

First of all, make sure to change the baseUrl to your own website URL. Then, you don't have to worry about running the script manually because Next.js will do it for you on build time. So when you build your Next.js application, the script will be executed (via Node.js) and the sitemap will be generated in the root directory of your website.

If for whatever reason you want to check the sitemap.xml file, you can run npm run build and it's going to be inside the build output directory (usually a folder called out). If you don't like what you see, you can always modify the script to fit your needs.

What is the benefit of this script over others?

I have found other scripts online that are not going deep into subdirectories and didn't have the flexibility I needed. This script is using recursion to get all the routes recursively from the src/app directory, then it generates a sitemap with the following properties:

  • url: The full URL of the route.
  • lastModified: The date when the route was last modified.
  • changeFrequency: How frequently the route is likely to change.
  • priority: The priority of the route relative to other routes on the site.

You do have the flexibility to change anything you want in the script, but I think it's a good starting point.

Conclusion

Thanks for reading guys and gals. I hope you found this blog post useful and you learned how to generate a sitemap for a Next.js application. Leave your feedback via e-mail at [email protected], I would love to hear from you.

Further reading