skies.dev

Dynamic Sitemap with Next.js and Supabase

2 min read

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
pages/sitemap.xml.tsx
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.

pages/sitemap.xml.tsx
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.

pages/sitemap.xml.tsx
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.

pages/sitemap.xml.tsx
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.

Hey, you! 🫵

Did you know I created a YouTube channel? I'll be putting out a lot of new content on web development and software engineering so make sure to subscribe.

(clap if you liked the article)

You might also like