How to Fix: No Images Sitemap on App Router
The bug is not in your images; it is in how the App Router sitemap metadata API serializes sitemap entries. In affected setups, adding images inside sitemap.ts does not produce an image sitemap payload, so the generated XML only includes page URLs and silently drops the expected <image:image> nodes.
Table of Contents
Understanding the Root Cause
In the Next.js App Router, app/sitemap.ts uses the metadata-based sitemap generator. The issue appears when developers follow the image sitemap documentation and return entries with an images field, but the framework version in use does not emit the corresponding image sitemap XML namespace and nested <image:image> tags.
Technically, the problem usually comes from one of these causes:
- The installed Next.js version has incomplete or inconsistent support for image sitemap serialization in the App Router.
- The
sitemap.tsfile returns a shape that is valid in TypeScript or documentation examples, but the runtime generator ignores image metadata. - The generated sitemap route is using the standard sitemap serializer, which outputs
<url>and<loc>correctly but omits image extensions. - There is confusion between a regular sitemap and a dedicated XML response route. If you need guaranteed image sitemap output, the metadata helper may not be sufficient for your version.
That is why you can see a working sitemap URL in the browser while still getting no image entries in the final XML.
Step-by-Step Solution
The most reliable fix is to stop depending on automatic image sitemap serialization in app/sitemap.ts and instead generate the XML manually through a route handler. This gives you full control over namespaces, escaping, and image nodes.
1. Verify the current behavior in app/sitemap.ts
If your file looks like this, it may type-check but still fail to output image nodes:
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
images: [
'https://example.com/images/hero.jpg',
],
},
]
}
If the generated XML only contains <loc>https://example.com</loc>, you are hitting the issue.
2. Replace metadata sitemap generation with a route handler
Create a dedicated route such as app/sitemap.xml/route.ts:
import { NextResponse } from 'next/server'
const baseUrl = 'https://example.com'
const pages = [
{
url: '/',
images: [
'/images/hero.jpg',
'/images/cover.jpg',
],
lastModified: new Date().toISOString(),
},
{
url: '/blog/nextjs-images',
images: [
'/images/blog/nextjs-images.jpg',
],
lastModified: new Date().toISOString(),
},
]
function escapeXml(value: string) {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(//g, '>')
}
export async function GET() {
const xml = `
${pages
.map(
(page) => `
${escapeXml(new URL(page.url, baseUrl).toString())}
${escapeXml(page.lastModified)}
${page.images
.map(
(image) => `
${escapeXml(new URL(image, baseUrl).toString())}
`
)
.join('')}
`
)
.join('')}
`
return new NextResponse(xml, {
headers: {
'Content-Type': 'application/xml; charset=utf-8',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
},
})
}
This approach bypasses the broken or incomplete serializer and guarantees valid image sitemap output.
3. Remove or rename the old app/sitemap.ts
Do not keep two competing sitemap implementations unless you intentionally want different endpoints. If app/sitemap.ts still exists, it may conflict with your custom route strategy or create confusion during testing.
app/
sitemap.xml/
route.ts
If you need a sitemap index later, create separate routes and reference them explicitly.
4. Confirm the XML contains the image namespace
After starting the app, open your sitemap endpoint and verify that the output includes both of these:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
<image:image>
<image:loc>https://example.com/images/hero.jpg</image:loc>
</image:image>
If the xmlns:image declaration or image:loc entries are missing, search engines will not treat it as an image sitemap.
5. Generate entries from dynamic content
For CMS-driven or database-backed sites, map your records into the same XML structure:
type SitemapItem = {
slug: string
updatedAt: string
imageUrls: string[]
}
async function getArticles(): Promise<SitemapItem[]> {
return [
{
slug: 'my-post',
updatedAt: new Date().toISOString(),
imageUrls: ['/images/my-post.jpg'],
},
]
}
Then transform them inside the route handler so each page emits its own image nodes.
6. Optional: keep using metadata sitemaps only after upgrading and validating
If you prefer app/sitemap.ts, upgrade to the latest stable Next.js release and test the output directly. Do not assume documentation parity across versions. The crucial point is not whether TypeScript accepts the images property, but whether your runtime actually renders valid XML.
Common Edge Cases
- Relative image URLs: Search engines expect absolute URLs in sitemap XML. Always resolve image paths against your site origin.
- Wrong namespace: If you forget
xmlns:image, crawlers may ignore image entries even though the XML looks close to correct. - Special characters: Unescaped query strings or ampersands in image URLs can break XML parsing. Always escape content.
- Mixed domains: If your app serves images from a CDN, ensure the CDN URLs are public and crawlable.
- Duplicate sitemap endpoints: Having both
sitemap.tsandsitemap.xml/route.tscan lead to testing the wrong URL. - Large sites: If you have thousands of pages, split sitemaps and create a sitemap index rather than returning one giant XML file.
- Build-time assumptions: Some developers expect static output, but route handlers may run dynamically depending on your configuration.
FAQ
Why does images in app/sitemap.ts compile but not show in the XML?
Because the issue is usually in the serializer implementation, not in the TypeScript type. A supported type shape does not always guarantee the generated sitemap includes image tags in your installed framework version.
Should I use app/sitemap.ts or a custom route.ts for image sitemaps?
If you need a guaranteed fix today, use a custom route handler. It gives you deterministic XML output and avoids framework-specific gaps in image sitemap support.
Do I need a separate image sitemap file?
Not necessarily. You can include image entries inside a standard sitemap as long as the XML namespace and nested image tags are valid. A separate sitemap is only necessary if you want to organize large datasets or split sitemap responsibilities.
The practical takeaway is simple: when App Router fails to emit image nodes from sitemap.ts, switch to a manual XML route. It is the fastest path to a valid, crawlable image sitemap until framework support fully matches the documentation.