← Build logs
FrontendMay 17, 2026

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 = &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt; &lt;urlset xmlns=&quot;http://www.sitemaps.org/schemas/sitemap/0.9&quot; xmlns:image=&quot;http://www.google.com/schemas/sitemap-image/1.1&quot;&gt; ${sitemapEntries.map(entry =&gt; <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 => &lt;image:image&gt; &lt;image:loc&gt;${image.url}&lt;/image:loc&gt; &lt;image:caption&gt;${image.alt || ''}&lt;/image:caption&gt; &lt;/image:image&gt;).join('') || ''} </url> ).join('')} &lt;/urlset&gt;;

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 with curl or other tools whether specific features like the images field 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.