skies.dev

Dynamic Sitemap with Next.js and Supabase

2 min read

Start by creating pages/sitemap.xml.tsx.

Querying Supabase for Pages

We manage pages and their metadata in a post table.

We'll write a query that selects pages

  • 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 this implementation as needed, whether that's using Supabase, the filesystem, or any other CMS.

With url and date_modified for each page handy, we can build a sitemap.

Generating the Sitemap in JavaScript

Our getSitemap function iterates over the pages we fetched in the previous step and generates a <url> component for each.

I'm using date-fns to format date_modified (stored as ISO) in a way that's consistent with Google's documentation on how to build 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.

Generating the Sitemap in getServerSideProps

We'll set Content-Type, write our sitemap, and return an empty set of props.

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 we visit the site at /sitemap.xml, we should see our sitemap, ready to be crawled.

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