DEV Community

Cover image for How to Create Fully Customized Sitemap in NextJS using Custom Postbuild Script
Sohail SJ | chakraframer.com
Sohail SJ | chakraframer.com

Posted on

How to Create Fully Customized Sitemap in NextJS using Custom Postbuild Script

In last two blogs, I explained how to create a sitemap for a website in your NextJS project using the next-sitemap package and manually. In this blog, I will explain how you can create a fully customized sitemap for your website in NextJS using a custom postbuild script.

But if you are rocking a static website, you can use the next-sitemap package to create a sitemap as it will be much easier and faster. But if you are looking to create a fully customized sitemap, you can use the custom postbuild script approach.

Why this technique is important? Because sometimes you need to create a sitemap with a specific requirement like

  • Each year/folder/collection etc will have its own sitemap
  • Exclude some pages from the sitemap
  • Add custom priority, changefreq, and lastmod for each page
  • Add custom tags for each page and many more.

But unlike our first article in series, we will do it manually. This is will fully automated and you can customize it as per your requirements. So brew a coffee and lets get started ๐Ÿš€

Complementary Video Tutorial

If you prefer a visual walkthrough, check out this helpful video that complements the steps in this blog:

"Click the above player or here to watch the video on YouTube."

Prerequisites

  • Basic knowledge of NextJS
  • Basic knowledge of JavaScript
  • Basic knowledge of XML

Table of Content

Step 1: Create a custom postbuild script

Create a scripts folder in the root of your project and add a generate-sitemap.mjs file in it.

Step 2: Install globby package

npm install globby
Enter fullscreen mode Exit fullscreen mode

Wait a minute, what is globby? and why we need it?

Globby is a package that helps you to match files using the patterns the shell uses, like stars and stuff. We will use it to get all the pages from project build folder.

Step 3: Add the following code in the generate-sitemap.mjs file

Before I share whole code, I would like to explain few key points in the code.

  • siteUrl: Base URL of your website
  • fullUrl: Function that will return the full URL of the page ie siteUrl + path
  • defaultConfig: Default configuration for each page in the sitemap
  • homeConfig: Configuration for the home page
  • nextApproach: Approach you are using for the your NextJS project, it can be app or pages,

nextApproach is important because the build path for NextJS pages router is .next/server/pages and for NextJS app router is .next/server/app. So depending on the approach you are using, you need to change the path in the code.

import { writeFileSync } from 'fs'
import { globby } from 'globby'

const siteUrl = 'https://example.com'
const fullUrl = (path) => siteUrl + path
const defaultConfig = {
  changefreq: 'weekly',
  priority: '0.7',
  lastmod: new Date().toISOString(),
}

const homeConfig = {
  loc: '/',
  changefreq: 'monthly',
  priority: '1.0',
  lastmod: defaultConfig.lastmod,
}

const nextApproach = 'app' // app or pages
const serverPath = `.next/server/${nextApproach}`

async function generateSitemap() {
  // Grub Pages from build
  const buildPages = await globby([
    // *** Include ***
    // include all html files and nested html files
    `${serverPath}/*.html`,
    `${serverPath}/**/*.html`,
    // *** Exclude ***
    // /index.html is same as / so we will exclude it, along with 404, 500 and _not-found pages, cool right?
    `!${serverPath}/index.html`,
    `!${serverPath}/404.html`,
    `!${serverPath}/_not-found.html`,
    `!${serverPath}/500.html`,
  ])

  const sitemapStr = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
            <url>
                    <loc>${fullUrl(homeConfig.loc)}</loc>
                    <lastmod>${homeConfig.lastmod}</lastmod>
                    <changefreq>${homeConfig.changefreq}</changefreq>
                    <priority>${homeConfig.priority}</priority>
            </url>
            ${buildPages
        .map((page) => {
          const path = page.replace(serverPath, '').replace('.html', '')
          const loc = fullUrl(path)
          const lastmod = new Date().toISOString()
          let changefreq = defaultConfig.changefreq
          let priority = defaultConfig.priority
    if (path === '/products') {
            priority = '0.9' // Higher priority for the index products page
            changefreq = 'daily'
          } else if (path.includes('/products/')) {
            priority = '0.6' // Higher priority for the slug products page
            changefreq = 'daily'
          }
          return `<url>
                                <loc>${loc}</loc>
                                <lastmod>${lastmod}</lastmod>
                                <changefreq>${changefreq}</changefreq>
                                <priority>${priority}</priority>
                        </url>
                    `
        })
        .join('')}
    </urlset>
    `

  writeFileSync(`public/sitemap.xml`, sitemapStr)
}

generateSitemap()
Enter fullscreen mode Exit fullscreen mode

This code will produce a single sitemap.xml file in the public folder of your project.

But if you want to create multiple sitemaps, you create a multiple globby configurations and write multiple sitemap files in the public folder.

Continue reading to learn how to create multiple sitemaps or skip to next step if you only want to create a single sitemap.

Very Important Point below if you going to have multiple sitemaps in your project.

  • First Point: Include all the sitemaps in the robots.txt file, so that search engines can find all the sitemaps.
  • Second Point: Create a Index sitemap file that will include all the sitemaps links.

Side Quest: How to create multiple sitemaps

To create multiple sitemaps, you can create multiple globby configurations and write multiple sitemap files in the public folder.

import { writeFileSync } from 'fs'
import { globby } from 'globby'

const siteUrl = 'https://example.com'
const fullUrl = (path) => siteUrl + path
const defaultConfig = {
  changefreq: 'weekly',
  priority: '0.7',
  lastmod: new Date().toISOString(),
}

const homeConfig = {
  loc: '/',
  changefreq: 'monthly',
  priority: '1.0',
  lastmod: defaultConfig.lastmod,
}

const nextApproach = 'app' // app or pages
const serverPath = `.next/server/${nextApproach}`

async function generateSitemap() {
  // Grub Pages from build for products
  const productPages = await globby([
    // *** Include ***
    // include all html products files and nested html files
    `${serverPath}/products/*.html`,
    `${serverPath}/products/**/*.html`,
    // *** Exclude ***
    // /index.html is same as / so we will exclude it
    `!${serverPath}/products/index.html`,
  ])

  // Create multiple sitemaps
  const productSitemapStr = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
            <url>
                    <loc>${fullUrl('/products')}</loc>
                    <lastmod>${new Date().toISOString()}</lastmod>
                    <changefreq>daily</changefreq>
                    <priority>0.9</priority>
            </url>
            ${productPages
        .map((page) => {
          const path = page.replace(serverPath, '').replace('.html', '')
          const loc = fullUrl(path)
          const lastmod = new Date().toISOString()
          const changefreq = 'daily'
          const priority = '0.8'
          return `<url>
                                <loc>${loc}</loc>
                                <lastmod>${lastmod}</lastmod>
                                <changefreq>${changefreq}</changefreq>
                                <priority>${priority}</priority>
                        </url>
                    `
        })
        .join('')}
    </urlset>
    `

  writeFileSync(`public/product-sitemap.xml`, productSitemapStr)

  // You can do the same for other sitemaps. But lets move on to the next step.

  // lets create a sitemap for rest of the pages

  const restPages = await globby([
    // *** Include ***
    // include all html files and nested html files
    `${serverPath}/*.html`,
    `${serverPath}/**/*.html`,
    // *** Exclude ***
    // /index.html is same as / so we will exclude it, along with 404, 500 and _not-found pages, cool right?
    `!${serverPath}/index.html`,
    `!${serverPath}/404.html`,
    `!${serverPath}/_not-found.html`,
    `!${serverPath}/500.html`,
    // Exclude products pages
    `!${serverPath}/products/*.html`,
    `!${serverPath}/products/**/*.html`,
  ])

  const restSitemapStr = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
            <url>
                    <loc>${fullUrl(homeConfig.loc)}</loc>
                    <lastmod>${homeConfig.lastmod}</lastmod>
                    <changefreq>${homeConfig.changefreq}</changefreq>
                    <priority>${homeConfig.priority}</priority>
            </url>
            ${restPages
        .map((page) => {
          const path = page.replace(serverPath, '').replace('.html', '')
          const loc = fullUrl(path)
          const lastmod = new Date().toISOString()
          let changefreq = defaultConfig.changefreq
          let priority = defaultConfig.priority

          return `<url>
                                <loc>${loc}</loc>
                                <lastmod>${lastmod}</lastmod>
                                <changefreq>${changefreq}</changefreq>
                                <priority>${priority}</priority>
                        </url>
                    `
        })
        .join('')}
    </urlset>
    `

  writeFileSync(`public/rest-sitemap.xml`, restSitemapStr)

  // Create a index sitemap file
  const indexSitemapStr = `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        <sitemap>
            <loc>${fullUrl('/product-sitemap.xml')}</loc>
            <lastmod>${new Date().toISOString()}</lastmod>
        </sitemap>
        <sitemap>
            <loc>${fullUrl('/rest-sitemap.xml')}</loc>
            <lastmod>${new Date().toISOString()}</lastmod>
        </sitemap>
    </sitemapindex>
    `

  writeFileSync(`public/sitemap.xml`, indexSitemapStr)
}

generateSitemap()
Enter fullscreen mode Exit fullscreen mode

This code will produce multiple sitemap files in the public folder of your project. You can create as many sitemaps as you want by creating multiple globby configurations and writing multiple sitemap files in the public folder.

Note: Include all the sitemaps in the robots.txt file, so that search engines can find all the sitemaps.

Step 4: Add the following script in your package.json

"scripts": {
    "build": "next build",
    "postbuild": "node ./scripts/generate-sitemap.mjs"
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Run the build command

npm run build
Enter fullscreen mode Exit fullscreen mode

Step 6: Check the sitemap.xml file in the public folder of your project

| app
    | /pages
    | /public
        | /sitemap.xml
        | /product-sitemap.xml // if you created multiple sitemaps
        | /rest-sitemap.xml // if you created multiple sitemaps
Enter fullscreen mode Exit fullscreen mode

on successful build, you will see a sitemap.xml file in the public folder of your project. If you created multiple sitemaps, you will see multiple sitemap files in the public folder.

Conclusion

With this approach, you can create a fully customized sitemap for your website in NextJS. You can create multiple sitemaps with specific requirements like each year/folder/collection etc will have its own sitemap, exclude some pages from the sitemap, add custom priority, changefreq, and lastmod for each page, add custom tags for each page, and many more.

But what if you have a dynamic website and data changes frequently? In the next blog, I will explain how you can extend this approach to create a include dynamic data in the sitemap.

Till then, Happy Coding ๐Ÿš€

Get in touch

Platform Handle
Youtube @thesohailjafri
X/Twitter @thesohailjafri
LinkedIn @thesohailjafri
Instagram @thesohailjafri
Github @thesohailjafri

Top comments (0)