Next.js 14 sitemap-image 버그 탈출! 동적 모드 직접 구현 가이드 (2026)
Next.js 14 동적 모드에서 sitemap-image 버그를 겪으셨나요? MetadataRoute.Sitemap의 한계를 극복하고 sitemap.xml을 직접 구현하는 방법을 안내합니다.
Next.js 14 sitemap-image 버그 탈출! 동적 모드 직접 구현 가이드 (2026)
요즘 Next.js 14로 프로젝트를 진행하면서 sitemap-image 확장에 발목 잡혔던 경험을 공유하려고 해요. 동적 모드에서 MetadataRoute.Sitemap 기능을 썼는데, 아무리 images 필드를 채워도 sitemap-image가 제대로 작동하지 않더라고요.
시도와 함정
처음에는 Next.js 14의 MetadataRoute.Sitemap 기능을 그대로 사용하려고 했어요. src/app/sitemap.ts 파일에 images 필드를 추가하고 curl로 확인해보니, 분명히 images: [...]를 넣었는데도 <image:image> 태그가 하나도 안 잡히는 거예요.
// src/app/sitemap.ts (기존 시도) 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 ]; }
curl -I https://example.com/sitemap.xml 명령어로 확인했을 때, images 필드가 제대로 반영되지 않는 것을 보니 뭔가 Next.js 내부에서 동적 모드와 MetadataRoute.Sitemap의 images 필드 간에 호환성 문제가 있는 것 같았어요. 3시간 정도 삽질 끝에 이 결론에 도달했죠.
원인
결론적으로, Next.js 14의 동적 모드에서 MetadataRoute.Sitemap 기능이 images 필드를 제대로 파싱해서 sitemap-image 확장에 반영하지 못하는 버그였습니다. Next.js 자체의 문제로 추정되는데, 직접 해결하기보다는 우회하는 방법을 찾아야 했어요.
해결
그래서 rss.xml/route.ts를 만들던 것처럼, sitemap XML을 직접 생성하는 방식으로 전환했습니다. src/app/sitemap.xml/route.ts 파일을 만들고 sitemap 프로토콜과 sitemap-image 확장을 직접 구현하는 거죠.
// 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', }, }); }
이렇게 하면 xmlns:image 네임스페이스를 명시적으로 추가하고, 각 URL에 대한 <image:image> 태그와 <image:loc> 태그를 직접 제어할 수 있어요. lastmod, changefreq, priority 같은 속성들도 직접 설정해주니 훨씬 안정적이더라고요.
결과
- sitemap-image 확장이 정상적으로 동작하여 검색 엔진이 이미지까지 인덱싱할 수 있게 되었습니다.
- SEO 최적화가 강화되어 검색 결과에서의 노출 가능성이 높아졌습니다.
- Next.js 버그에 대한 직접적인 의존성을 줄여 안정성을 확보했습니다.
정리 — 같은 함정 안 빠지려면
- [ ] Next.js 내장 기능 사용 시, 특히 동적 모드에서는 예상치 못한 버그가 있을 수 있다는 점을 항상 염두에 두세요.
- [ ]
MetadataRoute사용 시images필드 등 특정 기능이 제대로 동작하는지curl등으로 직접 검증하는 습관을 들이세요. - [ ] SEO 관련 기능은 직접 구현하는 것이 장기적으로 더 안정적이고 최적화에 유리할 수 있습니다.
- [ ]
sitemap.xml/route.ts와 같이 직접 XML을 생성하는 방식은 유연성이 높으니 필요하면 고려해보세요.
태그
📨 박주니에게 한마디
스팸·악성 메시지 방지를 위해 구글 로그인 후 메시지를 보낼 수 있어요. 비공개로 전달되며, 운영자 외에는 볼 수 없습니다.
Google 로그인 후 메시지 남기기