Dynamic Sitemap with Next.js and Supabase
If your content changes often, a static sitemap gets stale quickly. A dynamic sitemap lets you generate the XML from the same data source your site already uses.
Start by creating pages/sitemap.xml.tsx.
Querying Supabase for Pages
This example reads pages from a post table in Supabase.
The query should return pages that are:
- configured to be indexed
- that are published
- ordered by most recently modified
import {supabase} from '@lib/supabase';
import {DBPost} from '@lib/types';
async function getPages(): Promise<DBPost[]> {
const {data: pages, error} = await supabase
.from<DBPost>('post')
.select()
.eq('noindex', false)
.not('date_published', 'is', null)
.order('date_modified', {ascending: false});
if (error) {
console.error(`failed to fetch post data: ${error.message}`);
return [];
}
if (!pages) {
console.warn('did not get any pages back');
return [];
}
return pages;
}
Adapt the data source to your stack. The same pattern works with Supabase, the filesystem, a CMS, or any other place you store published pages.
Once you have url and date_modified for each page, building the sitemap is
just string assembly.
Generating the Sitemap in JavaScript
getSitemap turns each page into a <url> entry.
This example uses date-fns to format date_modified as YYYY-MM-DD, which
matches Google's guidance on building a sitemap.
import {DBPost} from '@lib/types';
import {format} from 'date-fns';
function getSitemap(pages: DBPost[]) {
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages
.map(
(page) => `<url>
<loc>${page.url}</loc>
<lastmod>${format(
new Date(page.date_modified),
'yyyy-MM-dd',
)}</lastmod>
</url>`,
)
.join('')}
</urlset>
`;
}
We have the pieces in place to write getServerSideProps.
Write the response in getServerSideProps
Set the response header, write the XML, and return an empty props object.
import {GetServerSideProps} from 'next';
export const getServerSideProps: GetServerSideProps<{}> = async ({res}) => {
res.setHeader('Content-Type', 'text/xml');
res.write(getSitemap(await getPages()));
res.end();
return {
props: {},
};
};
Finally, we'll appease the framework by exporting a default function from our route.
export default function Sitemap() {
return null;
}
Our sitemap is ready!
When you visit /sitemap.xml, the response should be valid XML and ready for
crawlers to consume.