How to generate sitemap.xml file for Static Site Generated (SSG) in NextJS

You might have built a blog/website with NextJS and now you want to add a sitemap.xml file to your website. Generating a sitemap.xml file for a static site generated (SSG) website built with Next.js is a crucial step for search engine optimization (SEO) and improving the discoverability of your content.

It can be easier to add a sitemap file for a website using server side rendering. However, when working with a static site, which is using md, sitemap generation becomes more challenging. The official Next.js sitemap documentation advises manually creating a sitemap generator function or using a basic JavaScript function for this purpose. Yet, this approach may not be suitable for larger sites containing thousands of links as adding those links in the file manually can be hectic. So, let’s look into possible methods to generate sitemaps.

Method 1 (Recommended): Use next-sitemap npm package

There is a package for nextJS which can generate sitemaps for you. The package is called next-sitemap. It is simple to use and can be easily integrated to your existing project.

Let’s first install the package in the project as a dev dependency

npm i --save-dev next-sitemap

Create a new file called next-sitemap.config.js in the root of your project and add this code:

/** @type {import('next-sitemap').IConfig} */
module.exports = {
  siteUrl: process.env.SITE_URL || 'https://example.com', // change it with your website link
  generateRobotsTxt: true, // (optional)
  // ...other options
};

Next, add a postbuild script in your package.json file:

{
  "build": "next build",
  "postbuild": "next-sitemap"
}

Finally, just run npm build and after building the project, it will automatically generate sitemaps. You can see them in the public folder. You can see full documentation on github.

Method 2: Write A Basic Generator Script

The other method could be to write a your own simple script to generate the sitemaps. However, this method does come with its own set of challenges. Fetching all the links accurately and crafting the script can be time-consuming, especially since the requirements can differ depending on your specific blog setup.

In my own experience, I manage websites that utilize MDX files as the foundation for blog posts. For such a setup, I’ve developed a basic script to automatically gather posts from the designated folder based on their slugs. However, it’s important to note that I still manually add links to pages, as my website doesn’t feature an extensive array of pages to include. This manual step may not be suitable for websites with a large number of pages.

First install the required package to generate xml file

npm i --save-dev xmlbuilder2

You might have to modify the script a bit but here is the example for MDX-based NextJS blogs:

const fs = require('fs');
const { join } = require('path');
const { create } = require('xmlbuilder2');

const baseUrl = 'https://yourwebsite.com';
const postsDirectory = join(process.cwd(), '_posts');
const publicDirectory = join(process.cwd(), 'public');
const sitemapPath = join(publicDirectory, 'sitemap.xml');

// add more page links in the array below
const pageRoutes = ['/'];

async function generateSitemap() {
  const postRoutes = getPostRoutes();
  const combinedRoutes = [...pageRoutes, ...postRoutes];
  const root = create({ version: '1.0' }).ele('urlset', {
    xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9',
  });

  for (const route of combinedRoutes) {
    const urlElement = root.ele('url');
    urlElement.ele('loc').txt(`${baseUrl}${route}`);
    urlElement.ele('lastmod').txt(generateTimeStamp());
  }

  const xml = root.end({ prettyPrint: true });
  fs.writeFileSync(sitemapPath, xml, 'utf-8');

  console.log('Generated Sitemap.xml file successfully in public folder');
}

function getPostRoutes() {
  const postsFiles = fs.readdirSync(postsDirectory);
  const posts = postsFiles.map((name) => {
    return '/posts/' + name.replace('.md', '');
  });
  return posts;
}

function generateTimeStamp() {
  const now = new Date();
  const year = now.getUTCFullYear();
  const month = String(now.getUTCMonth() + 1).padStart(2, '0');
  const day = String(now.getUTCDate()).padStart(2, '0');
  const hours = String(now.getUTCHours()).padStart(2, '0');
  const minutes = String(now.getUTCMinutes()).padStart(2, '0');
  const seconds = String(now.getUTCSeconds()).padStart(2, '0');
  const milliseconds = String(now.getUTCMilliseconds()).padStart(3, '0');

  return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}Z`;
}

generateSitemap();

The generated xml file is stored in public folder. You can update the build scripts to first generate new sitemap and then build it so the sitemap is included in the build folder. Something like this:

{
  "build": "node generateSitemap.js && next build",
}

If you have any other ideas for better ways, do share it with me and I can update this blog post to add them too.

Leave a Reply

Your email address will not be published. Required fields are marked *