← 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 = `<?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 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.