How to Fix: Dynamic Sitemap throwing 404 error on Nextjs 15
Dynamic sitemap 404 in Next.js 15 usually means the route was implemented with the wrong file convention, returned the wrong shape, or was placed where the App Router metadata system cannot discover it.
Table of Contents
In Next.js 15, a sitemap is not just another random API endpoint. It is a special metadata route handled by the App Router. If you create it with the wrong filename, put it in the wrong directory, mix Pages Router patterns with App Router conventions, or return invalid sitemap data, visiting sitemap.xml can result in a 404.
If you followed the official Next.js sitemap documentation but still get a 404, the fix is usually straightforward once you align your file structure with how metadata routes are resolved.
Understanding the Root Cause
The most important detail is this: dynamic sitemap generation in Next.js 15 is file-convention based. Next.js looks for a special file named sitemap.ts, sitemap.js, or equivalent inside the app directory tree.
When that file exists in the correct place, Next.js automatically exposes it as /sitemap.xml. You do not manually create a route called /sitemap.xml with a route handler unless you intentionally want a custom implementation.
A 404 commonly happens for one of these technical reasons:
- The file is not named exactly sitemap.ts or sitemap.js.
- The file is not inside the app directory.
- You created a file like app/sitemap.xml.ts or app/api/sitemap/route.ts expecting metadata behavior.
- You are using the Pages Router pattern in an App Router app.
- A route group, basePath, or deployment config changes the final URL you are testing.
- The app is not fully using the App Router, so metadata file conventions are not being picked up as expected.
- Your build or runtime setup prevents the sitemap file from being compiled correctly.
Another subtle point: sitemap.ts is not a page component. It should export a default function that returns an array of sitemap entries typed as MetadataRoute.Sitemap. If you export the wrong thing, the route may fail at build or runtime, and depending on setup, that can look like a missing route.
Step-by-Step Solution
Use the official metadata route convention exactly as intended.
1. Place the file in the correct location
Create this file:
app/sitemap.ts
If your app uses a root src directory, use:
src/app/sitemap.ts
Do not name it sitemap.xml.ts. Next.js automatically maps sitemap.ts to /sitemap.xml.
2. Export a valid sitemap function
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
{
url: 'https://example.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.8,
},
]
}
Once this file exists, the sitemap should be available at sitemap.xml.
3. For dynamic entries, fetch your data inside the sitemap function
import type { MetadataRoute } from 'next'
async function getPosts() {
const res = await fetch('https://example.com/api/posts', {
next: { revalidate: 3600 },
})
if (!res.ok) {
throw new Error('Failed to fetch posts')
}
return res.json()
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts()
const postUrls = posts.map((post: { slug: string; updatedAt: string }) => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1,
},
...postUrls,
]
}
This is the correct way to create a dynamic sitemap in Next.js 15 using the metadata API.
4. Verify that you are using the App Router
Your project should contain an app directory. For example:
app/
layout.tsx
page.tsx
sitemap.ts
If your project only uses pages/, the metadata route convention will not work there. In that case, you need either:
- To migrate to the App Router, or
- Implement a custom XML response route manually.
5. Do not put the sitemap inside app/api unless you want a custom endpoint
This is incorrect for the built-in metadata sitemap route:
app/api/sitemap/route.ts
That creates an API route like /api/sitemap, not /sitemap.xml.
If you intentionally want a manual XML route, that is a different implementation:
import { NextResponse } from 'next/server'
export async function GET() {
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
</url>
</urlset>`
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml',
},
})
}
But for the issue described, the recommended fix is to use app/sitemap.ts.
6. Restart the dev server after adding the metadata file
Next.js usually detects new files, but metadata route changes can be easier to validate after a full restart:
npm run dev
Then open the correct path in the browser: sitemap.xml.
7. If you use basePath, test the prefixed URL
If your next.config.js includes a basePath, your sitemap route may be served under that prefix.
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: '/docs',
}
module.exports = nextConfig
In that case, the sitemap may be available at /docs/sitemap.xml instead of the root path.
8. Example of a fully correct setup
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const routes = ['', '/about', '/blog']
return routes.map((route) => ({
url: `https://example.com${route}`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: route === '' ? 1 : 0.8,
}))
}
That is enough for Next.js to generate a working sitemap response at /sitemap.xml.
Common Edge Cases
- Using src/app: If your project is organized under src, the file must be src/app/sitemap.ts, not app/sitemap.ts.
- Wrong URL tested: The route is /sitemap.xml, not /sitemap.
- basePath enabled: The sitemap URL includes the base path prefix.
- Static export limitations: If you are exporting a static site or using deployment modes with restrictions, dynamic fetch behavior may require extra care.
- Bad fetch logic: If your data fetch throws, your route may fail even though the file exists. Check server logs.
- Environment-specific domain issues: Hardcoding production URLs while testing locally will not create a 404, but it can produce an invalid sitemap for search engines.
- Mixing route handlers and metadata routes: A route handler in another location does not replace the metadata route convention automatically.
- Monorepo confusion: Make sure you are editing the actual app package being served by Next.js.
- Build cache: After renaming or moving the sitemap file, remove stale build output and restart.
rm -rf .next
npm run dev
FAQ
Why does Next.js expect sitemap.ts instead of sitemap.xml.ts?
Because sitemap.ts is a special metadata file convention. Next.js automatically transforms it into the /sitemap.xml route. The filename is semantic to the framework, not a literal output path.
Can I generate a sitemap from database content in Next.js 15?
Yes. Export an async default function from app/sitemap.ts and return a MetadataRoute.Sitemap array built from your database or API results.
Why do I get 404 in development even though the file looks correct?
Most often it is one of these: the file is outside the app directory, the app is actually using the Pages Router, you are checking /sitemap instead of /sitemap.xml, or a basePath changed the final route.
The reliable fix for this issue is to treat the sitemap as a Next.js metadata route: create app/sitemap.ts, return a valid MetadataRoute.Sitemap array, and access it through /sitemap.xml. Once those three pieces match, the 404 is typically resolved.