
Creating a custom sitemap with dynamic content in Next.js
Web | ⌛ 5 min read
Sitemaps serve as machine-readable documents detailing information about web pages. They enable search engines to efficiently recognize and monitor your website's content.
In this guide we will go over configuring sitemaps for your Next.js (App Router) websites, that serve dynamic content such as blog posts!
We will start off by using the default example given to us from the Next.js official docs
This code should be put in a file in the 'src/app' directory called 'sitemap.ts':
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://acme.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://acme.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://acme.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
]
}
At the moment this just returns us a sitemap with a bunch of static pages. But what if we have dynamically changing pages such as posts or products to show, adding them manually won't exactly suffice. Don't worry, we will get into that right now.
Let's start by putting our static pages into a separate variable to keep things organized and to still allow the possibility of adding static pages:
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const static_objects = [
{
url: 'https://acme.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://acme.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://acme.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
];
return static_objects;
}
Now we will begin fetching our data from (in this case) an external API. Here it is important that we define our export function as async and set the return type to a promise of the sitemap, as such:
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
After this we will define a function outside of the above to fetch the data from the API:
//Get the post from the api
async function fetchPosts() {
const response = await fetch(`https://api.example.com/apiroute`, { next: { revalidate: 3600 }});
const data = await response.json();
return data.posts;
}
Important to note, when fetching data in Next, caching will be forced by default. This may lead to unexpected results, to decide how fetched data is being cached, a second parameter can be passed to the fetch function stating the caching behavior (above is set to revalidate cached data after 1 hour). For more information see: https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating
After we have create our fetch function, we will call it in our main function and wait for the Promise to resolve:
import { MetadataRoute } from 'next'
//Get the post from the api
async function fetchPosts() {
const response = await fetch(`https://api.example.com/apiroute`, { next: { revalidate: 3600 }});
const data = await response.json();
return data.posts;
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const static_objects = [
{
url: 'https://acme.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://acme.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://acme.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
];
// Initiate both requests in parallel
const postData = fetchPosts();
// Wait for the promises to resolve
const [posts] = await Promise.all([postData]);
return static_objects;
}
Now we have fetched our data, but we are not yet doing anything with it yet. The sitemap will still return just our static pages, for that to change we first have to do something with our data.
In this case i will loop over the posts and create a new object for each one, then pushing them to the static_objects array:
//Loop through the posts and add them to the sitemap
posts.map((post: any) => {
static_objects.push({
url: `https://roanvanderduim.nl/blog/${post.slug}`,
lastModified: new Date(post.updated_at),
changeFrequency,
priority: 1.0,
});
});
Now you may notice the use of the 'changeFrequency' variable here, this is to ensure that the change frequency is set correctly and to avoid any errors, this variable is defined in the top of the script as a type:
type changeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
And the default is set as followed:
const changeFrequency = 'weekly' as changeFrequency;
You can now apply this variable to all of your page objects (also the static ones), after all of this you should get the final result:
import { MetadataRoute } from 'next'
//Get the post from the api
async function fetchPosts() {
const response = await fetch(`https://api.example.com/apiroute`, { next: { revalidate: 3600 }});
const data = await response.json();
return data.posts;
}
type changeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const changeFrequency = 'weekly' as changeFrequency;
const static_objects = [
{
url: 'https://acme.com',
lastModified: new Date(),
changeFrequency,
priority: 1,
},
{
url: 'https://acme.com/about',
lastModified: new Date(),
changeFrequency,
priority: 0.8,
},
{
url: 'https://acme.com/blog',
lastModified: new Date(),
changeFrequency,
priority: 0.5,
},
];
// Initiate both requests in parallel
const postData = fetchPosts();
// Wait for the promises to resolve
const [posts] = await Promise.all([postData]);
//Loop through the posts and add them to the sitemap
posts.map((post: any) => {
static_objects.push({
url: `https://roanvanderduim.nl/blog/${post.slug}`,
lastModified: new Date(post.updated_at),
changeFrequency,
priority: 1.0,
});
});
return static_objects;
}
Now when opening your sitemap, you should get both your static and dynamic pages listed!
Of course there are still plenty of ways to customize this setup, but it is a easy way to get started with your own custom sitemap in Next.js.