Skip to main content
Main Article image

Generate a dynamic sitemap for your Next.js Prismic website

How to generate a hands off sitemap with Next.js and Prismic, utilising the new Prismic client v6 package and their new Route resolver

June 08, 2022

3 mins

Configure your Prismic Client

We'd be using the latest prismic@client v6 package which is a revamped version from v5 and adds several goodies to our arsenal.

In prismic-configuration.js file in your root directory, create a new Client function using the new createClient

import { createClient } from "@prismicio/client";

export const apiEndpoint = "https://your-repo.cdn.prismic.io/api/v2";

export const Client = (req = null, options = {}) =>
  createClient(apiEndpoint, Object.assign({ routes: Router.routes }, options));

Route resolver function

You can utilise Prismic's Route Resolver to create complex URLs which are impossible with the Link Resolver. This is what mine looks like in order to generate a content post with a folder/category in the URL.

export const Router = {
  routes: [
    {
      type: "content-post",
      path: "/:folder?/:uid/",
      resolvers: {
        folder: "folder",
      },
    },
  ],
  href: (type) => {
    const route = Router.routes.find((r) => r.type === type);
    return route && route.href;
  },
};

I've already included it in the Client initializer, which means that it'd be accessable via the .url prop instead of using the Link Resolver.

Configure your Link resolver function

For simple URLs which don't require the use of Route Resolver, you can utilise the Link Resolver function.

Create a new file utils/linkResolver.js

// Manages the url links to internal Prismic documents
// Expand this function as your website grows
const linkResolver = (doc) => {
  if (doc.type === "page" && doc.uid !== "home") {
    return `/${doc.uid}/`;
  }

  if (doc.type === "person") {
    return `/author/${doc.uid}/`;
  }

  if (doc.type === "page") {
    return `/${doc.uid}`
  }

  // using Route Resolver instead
  // https://prismic.io/docs/technologies/the-route-resolver-nextjs
  if (doc.type === "content-post") {
    return null;
  }
  
  return "/";
};

export default linkResolver;

A note that if you use both the Link Resolver and the Route resolver, Prismic will first check the Link Resolver. If it returns null, it will then fallback to the Route Resolver. This is why we return null for content-post documents.

Create a sitemap.xml.jsx file

Create a new file /pages/sitemap.xml.jsx

import React from "react";
import linkResolver from "utils/linkResolver";
import { Client } from "prismic-configuration";
import { renderToStaticMarkup } from "react-dom/server";

const SitemapIndex = () => null;

const Sitemap = ({ pages }) => {
  const origin = process.env.NEXT_PUBLIC_BASE_URL;

  return (
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      {pages?.map((page, index) => {
        const lastModified = new Date(page.last_publication_date).toISOString();

        // special rule for the Homepage
        if (
          linkResolver(page) === "/" &&
          page.type === "page" &&
          page.uid === "home"
        ) {
          return (
            <url key={index}>
              <loc>{origin}/</loc>
              <lastmod>{lastModified}</lastmod>
            </url>
          );
        }

        if (
          (linkResolver(page) !== "/" || page.url) &&
          !(page.data.seoIndex === false)
        ) {
          const url = origin + (linkResolver(page) || page.url);

          return (
            <url key={index}>
              <loc>{url}</loc>
              <lastmod>{lastModified}</lastmod>
            </url>
          );
        }
      })}
    </urlset>
  );
};

export const getServerSideProps = async ({ res }) => {
  const pages = await Client().dangerouslyGetAll();

  res.setHeader("Content-Type", "text/xml");
  res.write(renderToStaticMarkup(<Sitemap pages={pages} />));
  res.end();

  return {
    props: {},
  };
};

export default SitemapIndex;

Here's is a short snippet of what the output of the sitemap looks like:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://seocomponent.com/</loc>
    <lastmod>2022-02-15T15:07:05.000Z</lastmod>
  </url>
  <url>
    <loc>https://seocomponent.com/blog/last-updated-date/</loc>
    <lastmod>2022-02-16T13:24:43.000Z</lastmod>
  </url>
</urlset>

Origin URL

Remember to add a NEXT_PUBLIC_BASE_URL env to your local .env.local file as well as in your production website. For example, the env for this website is:

NEXT_PUBLIC_BASE_URL=https://seocomponent.com

await Client().dangerouslyGetAll()

We utilise the new dangerouslyGetAll() to get all documents from our Prismic repo.

Last modified date

The last modified date in the sitemap should be the date the page was last modified, no matter how small the change is. This will let Google know that it should recrawls the website in order to keep the records up to date. We utilise the built in last_publication_date field from Prismic.

Optional "seoIndex" field

You can add a seoIndex boolean field to your Prismic documents which default to true. If a document is set to false, it means that you won't don't want it indexed, thus you shoulnd't include it in your sitemap.

If you don't want to utilise this field in your website, you can delete this line from the code.

getServerSideProps()

We use Next.js' getServerSideProps() to run the page on request time, instead of build time. This means that we don't have to worry about generating the map during the build, since Google will always have the latest copy of our sitemap whenever it wishes to crawl it.

What about adding frequency and priority fields in the sitemap?

Google has mentioned many times that they ignore frequency and priority fields in sitemaps and instead you should prioritise including the loc and lastmod fields.

Update your robots.txt file

Before you launch your sitemap, don't forget to add it in your robots.txt file.

User-agent: *
Allow: /

Sitemap: https://www.seocomponent.com/sitemap.xml

Related Posts