Sitemap index #61025
Replies: 7 comments 13 replies
-
I made a PR that adds support for |
Beta Was this translation helpful? Give feedback.
-
I don't understand the utility of |
Beta Was this translation helpful? Give feedback.
-
Meanwhile, I went with my own custom index at import { env } from "~/env";
import { getLastMod } from "~/lib/sitemap";
import { generateSitemaps as getArtistSitemaps } from "../api/sitemaps/artists/sitemap";
import { generateSitemaps as getEventSitemaps } from "../api/sitemaps/events/sitemap";
import { generateSitemaps as getVenueSitemaps } from "../api/sitemaps/venues/sitemap";
export const revalidate = 3600; // cache for 1 hour
function getFileName(id?: number) {
if (id == null) {
return "sitemap.xml";
}
/**
* https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generating-multiple-sitemaps
* In production, your generated sitemaps will be available at /.../sitemap/[id].xml. For example, /product/sitemap/1.xml.
* In development, you can view the generated sitemap on /.../sitemap.xml/[id]. For example, /product/sitemap.xml/1. This difference is temporary and will follow the production format.
* See the generateSitemaps API reference for more information.
*/
return env.VERCEL_ENV === "development"
? `sitemap.xml/${id}`
: `sitemap/${id}.xml`;
}
function getLoc(path: string, id?: number) {
return `${env.URL}/api/sitemaps/${path}/${getFileName(id)}`;
}
function getSitemap(path: string, id?: number) {
return /* XML */ `<sitemap><loc>${getLoc(path, id)}</loc><lastmod>${getLastMod()}</lastmod></sitemap>`;
}
function getSitemaps(ids: { id: number }[], path: string) {
return ids.map(({ id }) => getSitemap(path, id)).join("");
}
export async function GET() {
const xml = /* XML */ `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${getSitemap("cities")}
${getSitemaps(await getEventSitemaps(), "events")}
${getSitemaps(await getArtistSitemaps(), "artists")}
${getSitemaps(await getVenueSitemaps(), "venues")}
</sitemapindex>
`;
return new Response(xml, {
headers: {
"Content-Type": "application/xml",
},
});
} |
Beta Was this translation helpful? Give feedback.
-
agreed, we need proper sitemap generation (incl. sitemap-index) |
Beta Was this translation helpful? Give feedback.
-
For anyone looking for a alternatives, I would recommend the server side utils from https://github.com/iamvishnusankar/next-sitemap?tab=readme-ov-file#server-side-sitemap-getserversidesitemap. You can plug them in into a route handler and it probably has all the features you would ever need. |
Beta Was this translation helpful? Give feedback.
-
I don't really understand the goal of generateSitemaps if it's just to generate the available url without indexing them. At least, MetadataRoute.Sitemap have to accept both url or sitemap link to allow dev to reference another sitemap file and generate an xml |
Beta Was this translation helpful? Give feedback.
-
Sharing the approach we’ve been using: We have a script that runs at the end of our build process. This script scans the app directory for all instances of We like this approach because, once it’s in place, all we need to do is create sitemap.ts instances following Next.js file conventions, with no additional work required. It’s also copy n' paste reusable across projects. Additionally, it even handles Here’s how you might integrate it into your package.json: "scripts": {
"build": "next build && pnpm sitemap:generate",
"sitemap:generate": "dotenv -c -- npx tsx ./scripts/sitemap.ts",
}, And here is the script... import { promises as fs } from 'fs';
import path from 'path';
import { SITE_URL } from '~/app/_lib/constants';
/**
* 🗺️ Generates the main sitemap index file.
*
* This function creates a sitemap index file by discovering and aggregating
* sitemap files generated across the project. It is temporary until Next.js
* offers native support for sitemap indexing.
*/
const ROOT_DIR = process.cwd();
const APP_DIR = path.join(ROOT_DIR, 'app');
async function main() {
try {
const sitemaps = await findSitemapFiles(APP_DIR);
const SITEMAP_INDEX_PATH = path.join(ROOT_DIR, 'public', '/sitemap.xml');
const createSitemap = (url: string) => /* XML */ `
<sitemap>
<loc>${url}</loc>
</sitemap>`;
const createSitemapIndex = (urls: string[]) => /* XML */ `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(createSitemap).join('')}
</sitemapindex>`;
const xml = createSitemapIndex(sitemaps);
await fs.mkdir(path.dirname(SITEMAP_INDEX_PATH), { recursive: true });
await fs.writeFile(SITEMAP_INDEX_PATH, xml.trim());
console.log(`Sitemap index generated with ${sitemaps.length} entries.`);
} catch (error) {
console.error('Error generating sitemap:', error);
}
}
/**
* 🔎 Recursively finds 'sitemap.ts' files and extracts sitemap URLs.
*
* Traverses directories from the specified start point, looking for 'sitemap.ts' files.
* If a file contains a `generateSitemaps` function, the function is invoked
* to retrieve sitemap URLs. If absent, a default sitemap URL is constructed.
*
* Known Limitations:
* - While this does consider Next.js conventions for folders like route groups, do not place
* 'sitemap.ts' under folders like route groups untill the following seperate issue is resolved:
* https://github.com/vercel/next.js/issues/68403
*
* @param {string} dir - The base directory to begin searching for sitemap files.
* @returns {Promise<string[]>} A promise that resolves to an array of sitemap URLs.
*/
async function findSitemapFiles(dir: string): Promise<string[]> {
let sitemaps: string[] = [];
try {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
if (file.isDirectory()) {
// ingore private folders since all its child segments are opted out of routing
// https://nextjs.org/docs/app/getting-started/project-structure#route-groups-and-private-folders
if (!file.name.startsWith('_')) {
const nestedSitemaps = await findSitemapFiles(fullPath);
sitemaps = sitemaps.concat(nestedSitemaps);
}
} else if (file.isFile() && file.name === 'sitemap.ts') {
const relativePath = path
.relative(APP_DIR, dir)
.split(path.sep)
.filter(
(segment) =>
// Exclude specific Next.js folder conventions that should not be part of the resolved path
// https://nextjs.org/docs/app/getting-started/project-structure#route-groups-and-private-folders
// https://nextjs.org/docs/app/getting-started/project-structure#parallel-and-intercepted-routes
// https://nextjs.org/docs/app/getting-started/project-structure#dynamic-routes
!/^[(\[@]/.test(segment),
)
.join('/');
const sitemapFile = await import(fullPath);
if (sitemapFile.generateSitemaps) {
const result = await sitemapFile.generateSitemaps();
result.forEach((item: { id: number }) => {
sitemaps.push(`${SITE_URL}/${relativePath}/sitemap/${item.id}.xml`);
});
} else {
sitemaps.push(`${SITE_URL}/${relativePath}/sitemap.xml`);
}
}
}
} catch (error) {
console.error('Error finding sitemaps:', error);
}
return sitemaps;
}
main(); |
Beta Was this translation helpful? Give feedback.
-
Goals
When using the multiple sitmap approach, Nextjs should produce a root sitemap that will point to all sitemap urlsets e.g /sitemap.xml
Non-Goals
Background
As you can see in the image, we should also have this sitemap pointing to multiple sitemaps

Taken from Google documentation on large sitemaps
Proposal
I propose Nextjs will keep track of all the generated sitemaps, and generate the proposed sitemap of sitemaps xml.
What do you think?
Beta Was this translation helpful? Give feedback.
All reactions