Dynamic Sitemap Generation for Next.js with Sanity Pages

4 replies
Last updated: Dec 6, 2021
Not so much a "getting started" question, but I am curious how others are handling a sitemap for their Next.js application with dynamic pages from Sanity. I am currently using
next-sitemap
but to pick any new pages up a new release of my app would have to be released. However, there is this snippet from
next-sitemap
for creating dynamic/server-side sitemaps... I just feel it is weird to have 2 sitemaps with duplicate content is just _odd_/wrong.
AI Update

You're right to feel that having two sitemaps with duplicate content is odd! The good news is that with modern Next.js (especially App Router), you don't actually need next-sitemap at all anymore. You can create a fully dynamic, server-side sitemap using Next.js's built-in MetadataRoute.Sitemap API.

The Modern Approach: Single Dynamic Sitemap

Instead of using next-sitemap, create a app/sitemap.ts (or app/sitemap.xml/route.ts) file that queries Sanity directly at request time. This gives you a single sitemap that's always up-to-date without needing redeployments:

// app/sitemap.ts
import { MetadataRoute } from 'next'
import { sanityFetch } from '@/sanity/lib/client'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const query = `*[_type in ["page", "post"] && defined(slug.current)] {
    "href": select(
      _type == "page" => "/" + slug.current,
      _type == "post" => "/posts/" + slug.current
    ),
    _updatedAt
  }`
  
  const pages = await sanityFetch({ query })
  
  return pages.map((page) => ({
    url: `https://yourdomain.com${page.href}`,
    lastModified: new Date(page._updatedAt),
    changeFrequency: 'weekly',
    priority: 0.8,
  }))
}

This approach is covered in detail in Sanity's dynamic sitemap guide.

Why This is Better

  1. No duplicate content - Single source of truth
  2. Always current - Reflects content changes immediately without redeployment
  3. Native Next.js - No extra dependencies
  4. Proper caching - Next.js handles revalidation automatically
  5. Uses Sanity's _updatedAt - Accurate lastModified dates for search engines

When You Might Still Use next-sitemap

The only real reason to keep next-sitemap is if you need its postbuild generation for static exports or have complex sitemap index splitting requirements. But for most Sanity + Next.js apps with dynamic content, the native Next.js approach is simpler and more reliable.

The "two sitemaps" pattern from next-sitemap's docs (using getServerSideSitemap in a server-side route like app/server-sitemap.xml/route.ts) was really a workaround before Next.js had first-class sitemap support. Now that Next.js has MetadataRoute.Sitemap in the App Router, you can do everything in one place without the awkwardness of merging static and dynamic sitemaps or dealing with duplicate entries.

Show original thread
4 replies
Hi
user P
, I’ve been creating my sitemaps using a
sitemap.xml.jsx
file in my pages directory. You could add your static pages here too.

import { allSlugsQuery } from '@/data/queries';
import { sanityClient } from '@/lib/sanity.server';

const SiteMap = function () {
	return <div>loading</div>;
};

export async function getServerSideProps({ res }) {
	const baseUrl = process.env.NEXT_PUBLIC_SITE_URL;
	const urls = await sanityClient.fetch(allSlugsQuery);
	const slugs = urls.map(
		(page) =>
			`
      <loc>${baseUrl}${page.replace('/', '')}</loc>
      <changefreq>daily</changefreq>
      <priority>0.7</priority>
    `
	);

	const locations = [...slugs];
	const createSitemap = () => `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="<http://www.sitemaps.org/schemas/sitemap/0.9>">
        ${locations.map((location) => `<url> ${location}</url>`).join('')}
    </urlset>
    `;
	res.setHeader('Content-Type', 'text/xml');
	res.write(createSitemap());
	res.end();
	return {
		props: {},
	};
}

export default SiteMap; 
and a groq query that pulls all my slugs


// GROQ All Slugs
export const allSlugsQuery = `
  *[defined(slug.current)][].slug.current
`;
I’ve set my site up using a blog post from
user T
that uses Next catch all routes and slugs: https://www.simeongriggs.dev/nextjs-sanity-slug-patterns
I hope this helps!
The only thing this doesn't account for in my scenario is having
/authors/[slug]
but also
/[slug]
I was running into the same issues when trying figure out my sitemap for Sanity projects.
With help from the the article linked above, I use 1 catch all route for all pages. The articles discusses strategies to define different slug patterns and applying different layouts layouts.

That above sitemap is from this site
https://www.sisucon.com.au/sitemap.xml and it outputs my
/careers/
pages well.
I usually create an api/sitemap function that generates the sitemap xml and then have a redirect called sitemap.xml to that function in next.config.js. No dependencies or build step, it's cacheable and customisable.
{ source: '/sitemap.xml', destination: '/api/sitemap' },
I use a catch all route as well, but not 'hard stored' like in the article above. I use a flexible parent based relation (references) for Sanity documents and have a groq sitemap query as the source of truth to resolve all routes. This query is used for the sitemap, next static paths, internal links and a visual sitemap inside sanity.

Routes are really key to your web app, best to have them set up right from the get go rather than as an afterthought.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?