DEV Community

Cover image for Sitemap with Next.js
Emil Privér
Emil Privér

Posted on • Updated on • Originally published at emilpriver.com

Sitemap with Next.js

OUTDATED! NEW WAY: https://emilpriver.com/blog/sitemap-with-nextjs-after-9-4-update

Originally written at: My website

So, Nextjs is really hyped and a really good framework for developing. Nextjs is shiped with alot of stuffs, but not an generated sitemap (Which i understand is not added by default in Nextjs).

This is 1 way to create an dynamic sitemap without creating a server to handle Nextjs and use getInitialProps instead as normal pages does.

URL

I wanted to be able to use /sitemap.xml as its a standard for sitemaps.

By testing out different type of filename did i later on understand that you are able to use

sitemap.xml.js 
Enter fullscreen mode Exit fullscreen mode

as name and it will be rendered when entering /sitemap.xml in your browser.

Something we will use in this small article.

Developing the XML feed.

Create a new file in pages/ folder in your projects map, the name of the file will be used when you want to access your sitemap.

import React from "react";
import axios from "axios";

const sitemapXML = data => {
  let latestPost = 0;
  let projectsXML = "";

  data.map(post => {
    const postDate = Date.parse(post.modified);
    if (!latestPost || postDate > latestPost) {
      latestPost = postDate;
    }

    const projectURL = `https://domain.ltd/project/${post.slug}/`;
    projectsXML += `
      <url>
        <loc>${projectURL}</loc>
        <lastmod>${postDate}</lastmod>
        <priority>0.50</priority>
      </url>`;
  });

  return `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      <url>
        <loc>https://domain.ltd/</loc>
        <lastmod>${latestPost}</lastmod>
        <priority>1.00</priority>
      </url>
      <url>
        <loc>https://domain.ltd/about/</loc>
        <priority>0.80</priority>
      </url>
      ${projectsXML}
    </urlset>`;
};

Enter fullscreen mode Exit fullscreen mode

First part of the code.
Here we create the XML feed with dynamic data.
We have some statics paths which is / and /about which we add manually.
but we also have some dynamic data, in my case is the dynamic data all my projects. So in the code do we loop thrue all my projects from an external source and add xml data to a variable which we later on uses to display the data.

class Sitemap extends React.Component {
  static async getInitialProps({ res }) {
    const data = await axios
      .get(
        "https://domain.ltd/some/url/"
      )
      .then(response => response.data);

    res.setHeader("Content-Type", "text/xml");
    res.write(sitemapXML(data));
    res.end();
  }
}

export default Sitemap;
Enter fullscreen mode Exit fullscreen mode

In the last part do we use axios to fetch data from an api. This works exactly the same way we do normally fetch data when using SSR with Next.JS.
But instead of using render() or return() do we directly send the XML feed to the client. So now we have working XML feed which is rendered SSR and ready when you need it.

And exempel using the method is my website: https://priver.dev/sitemap.xml

Github GIST if you want to copy the code

Hope this helps you :D

Discussion (24)

Collapse
tomgreeen profile image
tomgreeEn

Thanks for this Emil, very useful.

I'm just updating my app to take advantage of the new next getStaticProps feature, so I can render my sitemap.xml as a static page at build time, rather than generating it on the server every time.

Do you have any thoughts about how to achieve this ? the res object is not available in getStaticProps, so I can't find a way to serve the xml directly to the client as you do above without rendering it in a page component at which point it ceases to be an xml and appears as a string. Any suggestions?

Collapse
aralroca profile image
Aral Roca • Edited on

For sitemap I dunno. But for the rss.xml I just added this inside the getStaticProps of my /blog page:

fs.writeFileSync('public/rss.xml', rssData)

I dunno if is a good way, but it works 🤷‍♂️

Maybe with sitemap is it possible to do something similar?

Collapse
tomgreeen profile image
tomgreeEn

Thanks, I hadn't thought of that. I'll give it a try.

Thread Thread
juliepranger profile image
Julie Pranger

Any luck with this? I am currently working through the same issue.

Thread Thread
tomgreeen profile image
tomgreeEn

I got it working locally fine but not yet pushed to server (I'm using vercel now). Not sure if that allows fs.writes.

Will report back when I've pushed to the server in the next few days.

Thread Thread
aralroca profile image
Aral Roca

I use fs.writes but I added on webpack finally...

webpack(config, { isServer }) {
    // Generate Sitemap + RSS on build time
    if (isServer) {
      require('./utils/generateSitemap')()
      require('./utils/generateRss')()
    }
 return config
}
Thread Thread
ftpublic profile image
flashthepublic

Thanks!
I tried your solution but do you know how to implement it when you are using Typescript? i need to import existing logic to get data that is written in TS

Thread Thread
aralroca profile image
Aral Roca

The next.config.js file has js extension. So looks difficult to use TypeScript here. One thing you can do is compile next.config.ts to next.config.js: github.com/vercel/next.js/issues/5...

Collapse
nemuksis profile image
Paul van Dyk

Thanks for this method, i used another one which had to take advantage of experimental features, you could better this code using the sitemap package. eg;

import React from "react";
import { SitemapItem, EnumChangefreq } from "sitemap";
import { SitemapStream, streamToPromise } from "sitemap";
import { NextApiRequest, NextApiResponse, NextPageContext } from "next";

interface RouteItem extends Omit<SitemapItem, "img" | "links" | "video"> {
  title: string; // used as string title
  sitemap: boolean; // used to filter routes when construction sitemap
  navigation: boolean; // used to filter routes when construction navigation
  img?: SitemapItem["img"]; // transform type to optional
  links?: SitemapItem["links"]; // transform type to optional
  video?: SitemapItem["video"]; // transform type to optional
}

export const routes: RouteItem[] = [
  {
    title: "Landing",
    sitemap: true,
    navigation: false,
    url: "/",
    changefreq: EnumChangefreq.YEARLY,
  },
];

interface PageContext extends NextPageContext {
  req: NextApiRequest;
  res: NextApiResponse;
}

class Sitemap extends React.Component {
  static async getInitialProps({ req, res }: PageContext) {
    const hostname = "https://" + req.headers.host;
    res.setHeader("Content-Type", "text/xml");
    const smStream = new SitemapStream({ hostname });
    for (const route of routes) smStream.write(route);
    smStream.end();
    const sitemap = await streamToPromise(smStream).then((sm) => sm.toString());
    res.write(sitemap);
    res.end();
  }
}

export default Sitemap;
Enter fullscreen mode Exit fullscreen mode

@ook0 I agree with mark in using the sitemap package because it is typesafe and also has this possibility npmjs.com/package/sitemap#create-s...

Collapse
emil_priver profile image
Emil Privér Author • Edited on

My goal was to use as less packages as needed, there for didn`t I use sitemap package. But I think sitemap package is a better solution

Collapse
nemuksis profile image
Paul van Dyk • Edited on

Yes, I thought as much, I believe it's rather a choice of preference, thanks for the very informative post, both methods work great and in the end, it only comes down to a fight for bytes :)

BTW, here is the method I previously used, github.com/zeit/next.js/issues/905...
as you can see it has to leverage experimental features which are never good in a production env, so thanks for your method, I will be switching all my deployments to this method

Thread Thread
nemuksis profile image
Paul van Dyk • Edited on

and if you would like to go a step further, I included robots using the same method.

//robots.txt.ts
import React from "react";
import { NextApiRequest, NextApiResponse, NextPageContext } from "next";

interface PageContext extends NextPageContext {
  req: NextApiRequest;
  res: NextApiResponse;
}

class Robots extends React.Component {
  static async getInitialProps({ req, res }: PageContext) {
    const hostname = "https://" + req.headers.host;
    res.setHeader("Content-Type", "text/txt");
    res.write(`
    User-agent: *
    Sitemap: ${hostname}/sitemap.xml
    `);
    res.end();
  }
}

export default Robots;
Enter fullscreen mode Exit fullscreen mode
Collapse
freeadmiral profile image
Muhammed Enes Şahin

Hi ! great article thank you Emil. I implemented this post my next project it is working dev server but it fails in production. Why it is ? saalla.com/sitemap.xml . this my site. i dont see any error info at console. it just catch 500

Collapse
emil_priver profile image
Emil Privér Author

Hard for me to help you with a link. I recommend to check logs at Vercel :)

Collapse
freeadmiral profile image
Muhammed Enes Şahin

Have you ever encountered an error like this before? maybe it is related to next.config.js

Thread Thread
emil_priver profile image
Emil Privér Author

No I have not :/ I recommend to try other solutions as Next have new functions now, I am talking about getServerSidedProps for exempel ☺️

Thread Thread
freeadmiral profile image
Muhammed Enes Şahin • Edited on

thank you for replies. i ll look them :)

Collapse
alisahindev profile image
Ali Şahin

Thanks for this Emil, it is clearly. But i got an error in production like this.
[GET] /sitemap.xml
21:12:35:81
2020-07-17T18:12:35.996Z 022e5e7d-a417-40ba-84c0-66ba45030366 ERROR TypeError: Cannot assign to read only property 'undefined' of object '#'
at getServerSideProps (/var/task/.next/serverless/pages/sitemap.xml.js:10268:13)
at renderToHTML (/var/task/node_modules/next/dist/next-server/server/render.js:37:200)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async renderReqToHTML (/var/task/.next/serverless/pages/sitemap.xml.js:4554:22)
at async render (/var/task/.next/serverless/pages/sitemap.xml.js:4601:22)
it tried it getinitialprops and getserversideprops . i shared screenshot

Collapse
atularvind profile image
Atul Arvind

Hey Emil,
Thanks for the article it's really helpful. the link to your blog has 404.

Collapse
emil_priver profile image
Emil Privér Author

Thank you :D I've changed the url

Collapse
remjx profile image
Mark Jackson

Nice, I will combine this approach with the npm sitemap package npmjs.com/package/sitemap to generate the XML

Collapse
ook0 profile image
ook0

Hello Mark, why you need "sitemap" package?

Collapse
minasvisual profile image
Minas VIsual - Desenvolvimento

OMG.... finally a tutorial simple and objective! Thank you very much!

Collapse
emil_priver profile image
Emil Privér Author

I'm happy it helps :D