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;