DEV Community

Cover image for Sitemap with Next.js

Sitemap with Next.js

Emil Privér on February 03, 2020

OUTDATED! NEW WAY: https://emilpriver.com/blog/sitemap-with-nextjs-after-9-4-update Originally written at: My website So, Nextjs is rea...
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

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

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

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

thank you for replies. i ll look them :)

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 • Edited

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

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

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

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

I'm happy it helps :D

Collapse
 
peakdev118 profile image
PeakDev

I am very interested in your job.
I am a Next JS expert.
I want to work with you. Thanks