How to setup a dynamic sitemap.xml file on Netlify

I recently migrated my site to use Remix hosted on Netlify. I wanted to setup a dynamic sitemap.xml file using Netlify functions. Remix already runs on a Netlify function, so I just created a separate function for returning the sitemap. Here’s how I did it.

  1. Create a new sitemap folder in your netlify/functions directory with an index.ts file inside that directory.
Terminal window
mkdir netlify/functions/sitemap
touch netlify/functions/sitemap/index.ts
  1. Inside your new netlify/functions/sitemap/index.ts file, create a new handler that returns a basic XML file with no content.
netlify/functions/sitemap/index.ts
import type { Handler } from "@netlify/functions";

const handler: Handler = async () => {
  const xmlContent = [
    '<?xml version="1.0" encoding="UTF-8"?>',
    '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
    "</urlset>",
  ];

  return {
    statusCode: 200,
    headers: { "Content-Type": "text/xml" },
    body: xmlContent.join("\n"),
  };
};

export { handler };
  1. To test this out, let’s add a redirect from sitemap.xml to our newly created function. We can add it as a new redirect in the netlify.toml file. You can read more about file based configuration with Netlify here.
netlify.toml
[build]
  command = "remix build"
  functions = "netlify/functions"
  publish = "public"

[dev]
  command = "remix watch"
  port = 3000

[[redirects]]
  from = "/sitemap.xml"
  to = "/.netlify/functions/sitemap"
  status = 200

[[redirects]]
  from = "/*"
  to = "/.netlify/functions/server"
  status = 200

[[headers]]
  for = "/build/*"
  [headers.values]
    "Cache-Control" = "public, max-age=31536000, s-maxage=31536000"

Start up netlify dev and make a request to localhost:<YOUR PORT>/sitemap.xml.

You should see a basic XML file returned:

/sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
</urlset>
  1. Add your URLs to the sitemap.xml file dynamically using Javascript.
netlify/functions/sitemap/index.ts
import type { Handler } from "@netlify/functions";

const urls: Array<{ url: string; lastModified: string }> = [
  {
    url: "https://riegle.dev/",
    lastModified: "2022-01-01",
  },
  {
    url: "https://riegle.dev/blogs",
    lastModified: "2022-01-01",
  },
];

const handler: Handler = async () => {
  const xmlContent = [
    '<?xml version="1.0" encoding="UTF-8"?>',
    '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
    ...urls.map(({ url, lastModified }) => {
      return `
        <url>
          <loc>${url}</loc>
          <lastmod>${lastModified}</lastmod>
        </url>
      `.trim();
    }),
    "</urlset>",
  ];

  return {
    statusCode: 200,
    headers: {
      "Content-Type": "text/xml",
    },
    body: xmlContent.join("\n"),
  };
};

export { handler };

First, we define our site’s URLs in an array with both the URL and the last modified date. These are the only properties I care about on my site, but you can feel free to add any additional fields you want from the XML sitemap specification.

After we’ve created our list of URLs, we can add it to our xmlContent array by mapping each URL into an XML string.

Wrapping up

Make sure you add all your URLs into the URL list and keep it updated as you add additional URLs/update existing pages. This will ensure your pages gets indexed correctly by search engines.