How to generate a 'sitemap.xml' in my Next.js project on Vercel at runtime

Creating an api endpoint

First of all, we will create a new file on `pages/api/sitemap.js`. In this file we will write a handler that creates or sitemap dynamically.

export default async function handler(request, response) {
	response.statusCode = 200;
	response.setHeader('Content-Type', 'text/xml');

	// Instructing the Vercel edge to cache the file
	response.setHeader('Cache-control', 'stale-while-revalidate, s-maxage=3600');

	// generate sitemap here
	let xml = `<?xml version="1.0" encoding="UTF-8"?>
			<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 
				<url>
                  <loc>https://wardpoel.be</loc>
                  <lastmod>2023-03-29</lastmod>
                </url>
	  		</urlset>
		`;

	response.end(xml);
}

The sitemap we just made, will only include our homepage. We can easily add urls in the `<urlset>`. Let's say we have a blog on our site and want to include all our blogs in the sitemap.

export default async function handler(request, response) {
	response.statusCode = 200;
	response.setHeader('Content-Type', 'text/xml');

	// Instructing the Vercel edge to cache the file
	response.setHeader('Cache-control', 'stale-while-revalidate, s-maxage=3600');

	let blogs = await client.fetch(`*[_type == "blog"]{slug, _updatedAt} | order(date desc)`);

	// generate sitemap here
	let xml = `<?xml version="1.0" encoding="UTF-8"?>
			<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> 
				<url>
                  <loc>https://wardpoel.be</loc>
                  <lastmod>2023-03-29</lastmod>
                </url>
				<url>
                  <loc>https://wardpoel.be/blog</loc>
                  <lastmod>2023-03-29</lastmod>
                </url>
				${blogs.map(blog => `
                  <url>
                    <loc>https://wardpoel.be/blog/${blog.slug.current}</loc>
                    <lastmod>${blog._updatedAt}</lastmod>
                  </url>
                `)}
	  		</urlset>
		`;

	response.end(xml);
}

See how we add each blog to the sitemap `urlset` section. And we also included the blogs overview page.

Rewrite rule

The last step is to use a rewrite rule. This configuration will rewrite requests from `/sitemap.xml` to our custom `/api/sitemap` endpoint.

module.exports = {
  async rewrites() {
    return [
      {
        source: '/sitemap.xml',
        destination: '/api/sitemap',
      },
    ]
  },
}