loading...
Cover image for Sitemap with Next.js

Sitemap with Next.js

emil_priver profile image Emil Privér Updated on ・2 min read

OUTDATED! NEW WAY: https://priver.dev/posts/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 

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>`;
};

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;

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

Posted on by:

emil_priver profile

Emil Privér

@emil_priver

Self-taught developer at Rivercode. Based in Borås

Discussion

markdown guide
 

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

 

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

 

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

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

thank you for replies. i ll look them :)

 

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?

 

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?

 

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

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

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.

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
}
 

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;

@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...

 

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

 

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

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;
 

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

 

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

 

Hello Mark, why you need "sitemap" package?