Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sitemap image tag support #68034

Merged
merged 10 commits into from
Aug 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,46 @@ Output:
</urlset>
```

### Image Sitemaps

You can use `images` property for sitemap item to create image sitemaps. Learn more details in [Google Develop Docs for Image Sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps).
huozhi marked this conversation as resolved.
Show resolved Hide resolved

```ts filename="app/sitemap.ts" switcher
import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: '2021-01-01',
changeFrequency: 'weekly',
priority: 0.5,
images: ['https://example.com/image.jpg'],
},
]
}
```

Output:

```xml filename="acme.com/sitemap.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"
>
<url>
<loc>https://example.com</loc>
<image:image>
<image:loc>https://example.com/image.jpg</image:loc>
</image:image>
<lastmod>2021-01-01</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
</urlset>
```

### Generate a localized Sitemap

```ts filename="app/sitemap.ts" switcher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,32 @@ describe('resolveRouteData', () => {
"
`)
})
it('should resolve sitemap.xml with images', () => {
expect(
resolveSitemap([
{
url: 'https://example.com',
lastModified: '2021-01-01',
changeFrequency: 'weekly',
priority: 0.5,
images: ['https://example.com/image.jpg'],
},
])
).toMatchInlineSnapshot(`
"<?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">
<url>
<loc>https://example.com</loc>
<image:image>
<image:loc>https://example.com/image.jpg</image:loc>
</image:image>
<lastmod>2021-01-01</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
</urlset>
"
`)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ export function resolveSitemap(data: MetadataRoute.Sitemap): string {
const hasAlternates = data.some(
(item) => Object.keys(item.alternates ?? {}).length > 0
)
const hasImages = data.some((item) => Boolean(item.images?.length))

let content = ''
content += '<?xml version="1.0" encoding="UTF-8"?>\n'
content += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"'
if (hasImages) {
content += ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'
}
if (hasAlternates) {
content += ' xmlns:xhtml="http://www.w3.org/1999/xhtml">\n'
} else {
Expand All @@ -70,6 +74,11 @@ export function resolveSitemap(data: MetadataRoute.Sitemap): string {
}" />\n`
}
}
if (item.images?.length) {
for (const image of item.images) {
content += `<image:image>\n<image:loc>${image}</image:loc>\n</image:image>\n`
}
}
if (item.lastModified) {
const serializedDate =
item.lastModified instanceof Date
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/lib/metadata/types/metadata-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ type SitemapFile = Array<{
alternates?: {
languages?: Languages<string>
}
images?: string[]
}>

type ResolvingMetadata = Promise<ResolvedMetadata>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: '2024-01-01',
changeFrequency: 'daily',
priority: 0.5,
},
{
url: 'https://example.com/about',
lastModified: '2024-01-01',
images: [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
],
},
]
}
12 changes: 12 additions & 0 deletions test/e2e/app-dir/metadata-dynamic-routes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ describe('app dir - metadata dynamic routes', () => {
`<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/about" />`
)
})

it('should support images in sitemap', async () => {
const xml = await (await next.fetch('/sitemap-image/sitemap.xml')).text()

expect(xml).toContain(
`<image:image>\n<image:loc>https://example.com/image1.jpg</image:loc>\n</image:image>`
)
expect(xml).toContain(
`<image:image>\n<image:loc>https://example.com/image2.jpg</image:loc>\n</image:image>`
)
})

if (isNextStart) {
it('should optimize routes without multiple generation API as static routes', async () => {
const appPathsManifest = JSON.parse(
Expand Down
1 change: 1 addition & 0 deletions test/turbopack-build-tests-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2063,6 +2063,7 @@
"app dir - metadata dynamic routes sitemap should not throw if client components are imported but not used in sitemap",
"app dir - metadata dynamic routes sitemap should optimize routes without multiple generation API as static routes",
"app dir - metadata dynamic routes sitemap should support alternate.languages in sitemap",
"app dir - metadata dynamic routes sitemap should support images in sitemap",
"app dir - metadata dynamic routes sitemap should support generate multi sitemaps with generateSitemaps",
"app dir - metadata dynamic routes social image routes should fill params into dynamic routes url of metadata images",
"app dir - metadata dynamic routes social image routes should fill params into routes groups url of static images",
Expand Down
Loading