1

In my Next.js app, I'm trying to generate the robots.txt file dynamically. However, when I access http://localhost:3000/robots.txt, it redirects to the "Not Found" page. If I change the next.config.js to use http://localhost:3000/robots, it works correctly. I want http://localhost:3000/robots.txt to work instead.

This is my robots.ts file in the API folder.

import type { NextApiRequest, NextApiResponse } from 'next';
import { GraphQLRobotsService } from '@sitecore-jss/sitecore-jss-nextjs';
import { siteResolver } from 'lib/site-resolver';
import clientFactory from 'lib/graphql-client-factory';

const robotsApi = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
  res.setHeader('Content-Type', 'text/plain');

  // Resolve site based on hostname
  const hostName = req.headers['host']?.split(':')[0] || 'localhost';
  const site = siteResolver.getByHost(hostName);

  // create robots graphql service
  const robotsService = new GraphQLRobotsService({
    clientFactory,
    siteName: site.name,
  });

  const robotsResult = await robotsService.fetchRobots();

  return res.status(200).send(robotsResult);
};

export default robotsApi;

Also, this is my next.config.js file.

const jssConfig = require('./src/temp/config');
const plugins = require('./src/temp/next-config-plugins') || {};
const path = require('path');

const publicUrl = jssConfig.publicUrl;

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  // Set assetPrefix to our public URL
  assetPrefix: publicUrl,

  // Allow specifying a distinct distDir when concurrently running app in a container
  distDir: process.env.NEXTJS_DIST_DIR || '.next',

  // Make the same PUBLIC_URL available as an environment variable on the client bundle
  env: {
    PUBLIC_URL: publicUrl,
  },

  i18n: {
    // These are all the locales you want to support in your application.
    // These should generally match (or at least be a subset of) those in Sitecore.
    locales: ['ms', 'en', 'ms-MY'  ],
    // This is the locale that will be used when visiting a non-locale
    // prefixed path e.g. `/styleguide`.
    defaultLocale: jssConfig.defaultLanguage,
    localeDetection: false,
  },

  // Enable React Strict Mode
  reactStrictMode: true,

  // Disable the X-Powered-By header. Follows security best practices.
  poweredByHeader: false,

  // use this configuration to ensure that only images from the whitelisted domains
  // can be served from the Next.js Image Optimization API
  // see https://nextjs.org/docs/app/api-reference/components/image#remotepatterns
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'edge*.**',
        port: '',
      },
      {
        protocol: 'https',
        hostname: 'xmc-*.**',
        port: '',
      },
      {
        protocol: 'https',
        hostname: 'feaas*.blob.core.windows.net',
        port: '',
      },
    ]
  },

  async rewrites() {
    // When in connected mode we want to proxy Sitecore paths off to Sitecore
    return [

      {
        // when I change /robots.txt to /robots, http://localhost:3000/robots starts to work.
        source: '/robots.txt',
        destination: '/api/robots',
      },

      {
        source: '/sitecore/api/:path*',
        destination: `${jssConfig.sitecoreApiHost}/sitecore/api/:path*`,
      },
      // media items
      {
        source: '/-/:path*',
        destination: `${jssConfig.sitecoreApiHost}/-/:path*`,
      },
      // healthz check
      {
        source: '/healthz',
        destination: '/api/healthz',
      },
      // rewrite for Sitecore service pages
      {
        source: '/sitecore/service/:path*',
        destination: `${jssConfig.sitecoreApiHost}/sitecore/service/:path*`,
      },
    ];
  },
  sassOptions: {
    includePaths: [path.join(__dirname, 'src/assets')],
  },
};

module.exports = () => {
  // Run the base config through any configured plugins
  return Object.values(plugins).reduce((acc, plugin) => plugin(acc), nextConfig);
};

update: http://localhost/healthz correctly works, so my assumption is when I enter http://localhost:3000/robots.tsx, it tries to look for a static file. But there's no static file, so it goes to the Not Found page.

Update 2: I have changed the next.config.js file as @KateOrlova highlighted and this is my src/lib/next-config/plugins/robots.js file.

/**
 * @param {import('next').NextConfig} nextConfig
 */
const robotsPlugin = (nextConfig = {}) => {
  return Object.assign({}, nextConfig, {
    async rewrites() {
      const baseRewrites = (await nextConfig.rewrites?.()) || {
        beforeFiles: [],
        afterFiles: [],
        fallback: [],
      };

      baseRewrites.beforeFiles.push({
        source: '/robots.txt',
        destination: '/api/robots',
      });

      return baseRewrites;
    },
  });
};

module.exports = robotsPlugin;

1 Answer 1

1

The issue is that Next.js treats /robots.txt as a static file request before it applies your rewrites. Because of that your rewrite rule never executes and you get the 404 response back.

/healthz works because it doesn't have a file extension, so Next.js routes it normally.

To fix your issue you will need to move the rewrite to run before static file handling by using the beforeFiles phase in your rewrites() function.

Here is a code example:

async rewrites() {
  return {
    beforeFiles: [
      {
        source: '/robots.txt',
        destination: '/api/robots',
      },
    ],
    afterFiles: [
      {
        source: '/sitecore/api/:path*',
        destination: `${jssConfig.sitecoreApiHost}/sitecore/api/:path*`,
      },
      {
        source: '/-/:path*',
        destination: `${jssConfig.sitecoreApiHost}/-/:path*`,
      },
      {
        source: '/healthz',
        destination: '/api/healthz',
      },
      {
        source: '/sitecore/service/:path*',
        destination: `${jssConfig.sitecoreApiHost}/sitecore/service/:path*`,
      },
    ],
  };
}

Read more here.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi @KateOrlova, Thanks for the answer. I have added the changes you have given, but still I don't see the robots.txt file. I have made an update to the question also based on your answer.
@Lalinda, you need to handle the case where nextConfig.rewrites returns an array (old Next.js behaviour) above the new style you have with an object. For example, if (Array.isArray(baseRewrites)) {return [{ source: '/robots.txt', destination: '/api/robots' },..];}
Thank you. Your solution and the solution in the below link together helped me to solve the issue. github.com/vercel/next.js/discussions/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.