How to generate a dynamic sitemap for your Next JS website
A short tutorial to create a dynamic sitemap
Published: July 14, 2021
•
Updated: August 10, 2023
A sitemap will give your website pages more visibility on Google Search by defining the relationship between pages.
In this tutorial, I will walk you through how to create a static and dynamic sitemap for your Next JS website.
Static Sitemap
If you just need a simple sitemap, you can simply create a sitemap.xml.js
in your pages folder and structure it in the following way:
<urset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://deolaj.com</loc>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://deolaj.com/notes</loc>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
<urlset>
Dynamic Sitemap with local pages
To create a dynamic sitemap, first create a sitemap.js
file anywhere in the root of your project or in the src
folder.
Then add the globby
library to your project to get the list of routes:
yarn add globby --dev
First, to get the page routes, we will use the globby
package to extract the routes first. We exclude the Next JS default pages (_app.tsx
, _document.tsx
etc.), [slug].tsx
, and api
pages since they are not important.
const globby = require('globby');
const pages = await globby([
'pages/**/*.tsx',
'!pages/_*.tsx',
'!pages/**/[slug].tsx',
'!pages/api',
]);
Next, we get the current Date:
const currentDate = new Date().toISOString();
Next, we create the complete sitemap string
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('.js', '')
.replace('.tsx', '')
.replace('.md', '');
const route = path === '/index' ? '' : path;
return `
<url>
<loc>${`https://deolaj.com${route}`}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
</urlset>
`;
Next, write this file to your public folder:
const fs = require('fs');
fs.writeFileSync('public/sitemap.xml', sitemap);
Finally, put everything together
const fs = require('fs');
const globby = require('globby');
async function generateSiteMap() {
const pages = await globby([
'pages/**/*.tsx',
'!pages/_*.tsx',
'!pages/**/[slug].tsx',
'!pages/api',
]);
const currentDate = new Date().toISOString();
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('.js', '')
.replace('.tsx', '')
.replace('.md', '');
const route = path === '/index' ? '' : path;
return `
<url>
<loc>${`https://deolaj.com${route}`}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
</urlset>
`;
fs.writeFileSync('public/sitemap.xml', sitemap);
}
generateSiteMap();
Dynamic Sitemap with remote content
In this example, I will use contentful
to illustrate how to generate dynamic pages using remote content. E.g blog posts.
First, initialize the contentful client with your space id and access token
const { createClient } = require('contentful');
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
Get the posts from contentful and their paths (slug)
const notePosts = await client
.getEntries({ content_type: 'notes', order: 'sys.createdAt' })
.then((response) => {
// this is a custom utility function I created to extract the posts
const posts = generateNotePosts(response.items);
return posts;
});
const notePaths = notePosts.map(({ fields: { slug } }) => slug);
Next, modify the sitemap string with a new section for remote pages
...
${notePaths
.map((route) => {
return `
<url>
<loc>${`https://deolaj.com/notes/${route}`}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
...
Finally, put everything together
const fs = require('fs');
const globby = require('globby');
const { createClient } = require('contentful');
async function generateSiteMap() {
const pages = await globby([
'pages/**/*.tsx',
'!pages/_*.tsx',
'!pages/**/[slug].tsx',
'!pages/api',
]);
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const currentDate = new Date().toISOString();
const notePosts = await client
.getEntries({ content_type: 'notes', order: 'sys.createdAt' })
.then((response) => {
// this is a custom utility function I created to extract the posts
const posts = generateNotePosts(response.items);
return posts;
});
const notePaths = notePosts.map(({ fields: { slug } }) => slug);
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('.js', '')
.replace('.tsx', '')
.replace('.md', '');
const route = path === '/index' ? '' : path;
return `
<url>
<loc>${`https://deolaj.com${route}`}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
${notePaths
.map((route) => {
return `
<url>
<loc>${`https://deolaj.com/notes/${route}`}</loc>
<lastmod>${currentDate}</lastmod>
<changefreq>monthly</changefreq>
<priority>1.0</priority>
</url>
`;
})
.join('')}
</urlset>
`;
fs.writeFileSync('public/sitemap.xml', sitemap);
}
generateSiteMap();
Putting it together
To ensure that your page is built, add the following to your next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (isServer) {
// Replace the path below with the `sitemap.js` file location
require('./src/utils/sitemap');
}
return config;
},
};
Run your local server and the sitemap.xml file should appear in your public folder
Contact
Are you ready to work with me?
I'm actively open to new opportunities and requests.
If you have a question, or just want to say hi, I'll try my best to get back to you.