Escaping the Next.js 14 sitemap-image Bug! A Guide to Implementing Dynamic Mode Manually (2026)
Escaping the Next.js 14 Sitemap-Image Bug! A Guide to Implementing Dynamic Mode Manually (2026)
I wanted to share an experience I had recently while working on a project with Next.js 14, where I got stuck with the sitemap-image extension. I was using the MetadataRoute.Sitemap feature in dynamic mode, but no matter how much I filled out the images field, sitemap-image just wouldn't work correctly.
Attempts and Pitfalls
At first, I tried to use Next.js 14's MetadataRoute.Sitemap feature as is. I added the images field in the src/app/sitemap.ts file and checked it with curl. Even though I clearly included images: [...], not a single <image:image> tag was picked up.
// src/app/sitemap.ts (Initial Attempt)
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com/posts/1',
lastModified: new Date('2026-05-17'),
images: [
{
url: 'https://example.com/images/post1.jpg',
alt: 'Post 1 Image',
width: 1200,
height: 630,
},
],
},
// ... other URLs
];
}
When I checked with the command curl -I https://example.com/sitemap.xml, it seemed like there was a compatibility issue within Next.js between dynamic mode and the images field of MetadataRoute.Sitemap, as the images field wasn't being reflected properly. I reached this conclusion after about 3 hours of debugging.
The Cause
In conclusion, it was a bug where Next.js 14's dynamic mode couldn't properly parse the images field in MetadataRoute.Sitemap and reflect it in the sitemap-image extension. It seemed to be an issue with Next.js itself, so instead of trying to fix it directly, I had to find a workaround.
The Solution
So, I switched to generating the sitemap XML manually, similar to how I would create rss.xml/route.ts. I created a src/app/sitemap.xml/route.ts file and implemented the sitemap protocol and sitemap-image extension myself.
// src/app/sitemap.xml/route.ts
import { MetadataRoute } from 'next';
export async function GET() {
const sitemapEntries: MetadataRoute.Sitemap = [
{
url: 'https://example.com/posts/1',
lastModified: new Date('2026-05-17'),
changefreq: 'weekly',
priority: 0.9,
images: [
{
url: 'https://example.com/images/post1.jpg',
alt: 'Post 1 Image',
width: 1200,
height: 630,
},
],
},
{
url: 'https://example.com/about',
lastModified: new Date('2026-05-17'),
changefreq: 'monthly',
priority: 0.7,
},
];
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
${sitemapEntries.map(entry => `
<url>
<loc>${entry.url}</loc>
<lastmod>${entry.lastModified.toISOString().split('T')[0]}</lastmod>
<changefreq>${entry.changefreq || 'daily'}</changefreq>
<priority>${entry.priority || 0.5}</priority>
${entry.images?.map(image => `
<image:image>
<image:loc>${image.url}</image:loc>
<image:caption>${image.alt || ''}</image:caption>
</image:image>`).join('') || ''}
</url>
`).join('')}
</urlset>`;
return new Response(xml, {
headers: {
'Content-Type': 'application/xml',
},
});
}
By doing this, I explicitly added the xmlns:image namespace and could directly control the <image:image> and <image:loc> tags for each URL. Attributes like lastmod, changefreq, and priority were also set directly, making it much more stable.
Results
- The sitemap-image extension now works correctly, allowing search engines to index images.
- SEO optimization has been strengthened, increasing the chances of visibility in search results.
- Stability has been secured by reducing direct dependency on Next.js bugs.
In Summary — To Avoid the Same Pitfall
- [ ] Always keep in mind that unexpected bugs can occur when using built-in Next.js features, especially in dynamic mode.
- [ ] When using
MetadataRoute, make it a habit to verify directly withcurlor other tools whether specific features like theimagesfield are working correctly. - [ ] For SEO-related features, implementing them directly can be more stable and beneficial for optimization in the long run.
- [ ] The approach of generating XML directly, like with
sitemap.xml/route.ts, offers high flexibility, so consider it if necessary.