Generating Sitemap with NextJS + Typescript

Photo by Susan Q Yin on Unsplash

Generating Sitemap with NextJS + Typescript

Learn how to add a sitemap for dynamic and static routes in NextJs for better SEO.

I was asked by my organization to generate a sitemap for the application we were building. It took me a whole week of research and trying out different things before I was able to successfully get it done. So I've decided to save others the trouble by providing a pretty easy solution that works both in production and development mode.

What is a Sitemap?

A sitemap shows the pages and routes on a website. It is a map of the URLs on your site that makes it easier for search engines to index your content, increasing the likelihood of ranking in search results.

Why do I need to add a sitemap to my application?

Search engines like Google read this file to crawl websites, so if you are interested in making your website more SEO friendly, It is rather important to have a sitemap file.

Now let's get to it.

Generating a sitemap file in your Nextjs application

If you already have your Nextjs application ready, navigate to the pages directory, and create a sitemap.xml.tsx file in the root of the directory as so.

Capture.PNG

In this file, we are going to write a piece of code that generates an XML file which we can see when we navigate to localhost:3000/sitemap.xml.

import { NextApiResponse } from "next";
import { adPaths } from "../utils/sitemap/ad";
import { categoryPaths } from "../utils/sitemap/category";
import { dashboardPath } from "../utils/sitemap/dashboard";
import { sellPath } from "../utils/sitemap/sell";


const Sitemap = () => {
  return null;
};
const dashboardpath = dashboardPath();
const sellpath = sellPath();
;
export const getServerSideProps = async ({ res }: { res: NextApiResponse }) => {

  const adpaths = await adPaths();
  const categorypaths = await categoryPaths();



//spreading adPaths and categoryPaths into allPaths array
  const allPaths = [
    ...adpaths,
    ...categorypaths,
    dashboardpath,
    sellpath,

  ];

  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
   <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
     ${allPaths
       .map((url) => {
         return `
           <url>
             <loc>${url}</loc>
             <lastmod>${new Date().toISOString()}</lastmod>
             <changefreq>monthly</changefreq>
             <priority>1.0</priority>
           </url>
         `;
       })
       .join("")}
   </urlset>
 `;

  res.setHeader("Content-Type", "Text/xml");
  res.write(sitemap);
  res.end();
  return {
    props: {},
  };
};
export default Sitemap;

In the snippet above we made use of NextJS getServerSideProps This is because we want this file to be pre-rendered before being called on the client (browser) for better SEO.

Note that allPaths array is a concatenation of other arrays, which contains our dynamic and static paths.

Let us take a look at what is in adPaths as an example for generating a dynamic file. Note that this file can be in your utils directory outside pages, for the purpose of writing cleaner code.

utils.PNG

// adPaths.tsx
import { Api } from "services/Api";
import { IdType } from "types/types";

const BASE_URL = "https://localhost:3000";

export const adPaths = async () => {
  const allAdsResponse = await Api.staticIds.getAdIds(); // can be any api call
  const ads = allAdsResponse?.data?.data as IdType[];


  const paths = ads?.map((staticPagePath) => {
    return `${BASE_URL}/ad/${staticPagePath.slug}`; //returning all the dynamic paths
  });

  return paths;
};

We made an async function that makes an API call and returns an array of data, which we then map returning what would be our list of dynamic paths for ads.

Now let's take a look at sellPath as an example for generating a static file path.

// sellPath.tsx
const BASE_URL = "https://localhost:3000";

export const sellPath = () => {
  return `${BASE_URL}/sell.html`;
};

This file simply returns the file path for that file.

which brings us back to our sitemap.xml.tsx file where we have all our paths concatenated to our allPaths array using the spread operator

import { NextApiResponse } from "next";
import { adPaths } from "../utils/sitemap/ad";
import { categoryPaths } from "../utils/sitemap/category";
import { dashboardPath } from "../utils/sitemap/dashboard";
import { sellPath } from "../utils/sitemap/sell";


const Sitemap = () => {
  return null;
};
const dashboardpath = dashboardPath();
const sellpath = sellPath();


//calls our api on the serverside, and enables pre rendering of the data before the client is loaded
export const getServerSideProps = async ({ res }: { res: NextApiResponse }) => {

// triggers the api call
  const adpaths = await adPaths();  
  const categorypaths = await categoryPaths(); 



//spreading adPaths and categoryPaths into allPaths array
  const allPaths = [
    ...adpaths,  
    ...categorypaths,
    dashboardpath,
    sellpath,

  ];

  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
   <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
     ${allPaths
       .map((url) => {
         return `
           <url>
             <loc>${url}</loc>
             <lastmod>${new Date().toISOString()}</lastmod>
             <changefreq>monthly</changefreq>
             <priority>1.0</priority>
           </url>
         `;
       })
       .join("")}
   </urlset>
 `;

  res.setHeader("Content-Type", "Text/xml");
  res.write(sitemap);
  res.end();
  return {
    props: {},
  };
};
export default Sitemap;

NB: It is possible to generate the file paths using fs.readdirSync() but this might lead to some build errors when trying to deploy your application, as I experienced.

In the return block of our sitemap variable, we pass our URL to the loc tag which specifies the location of the URL. We also pass the current date and time to the lastmod tag which represents when the content at the specified URL was last modified. The changefreq tag specifies how frequently the content at the URL is updated, and finally, priority tag specifies the importance of the URL. We have passed 1.0 to it, which is the maximum level of importance.

  res.setHeader("Content-Type", "Text/xml");
  res.write(sitemap);
  res.end();

This block of code is used to set the header for the response and also write the sitemap data to the response body.

Now we can save all and navigate to localhost:3000/sitemap.xml

Here is mine..

image.png

image.png

In conclusion, we have been able to generate a sitemap.xml file using NextJS + Typescript. We also put to good use NextJS server-side rendering abilities.

With this, our site can be crawled and indexed efficiently by search engines like Google.

So, that's it!

Have a lovely day :)