Skip to content

Commit bedc31b

Browse files
jdcolombosarah11918florian-lefebvre
authored
feat(sitemap): add namespaces configuration option (#14285)
Co-authored-by: Sarah Rainsberger <[email protected]> Co-authored-by: Florian Lefebvre <[email protected]>
1 parent a89fb9f commit bedc31b

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

.changeset/rich-insects-scream.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
'@astrojs/sitemap': minor
3+
---
4+
5+
Adds a new configuration option `namespaces` for more control over XML namespaces used in sitemap generation
6+
7+
Excluding unused namespaces can help create cleaner, more focused sitemaps that are faster for search engines to parse and use less bandwidth. If your site doesn't have news content, videos, or multiple languages, you can exclude those namespaces to reduce XML bloat.
8+
9+
The `namespaces` option allows you to configure `news`, `xhtml`, `image`, and `video` namespaces independently. All namespaces are enabled by default for backward compatibility and no change to existing projects is necessary. But now, you can choose to streamline your XML and avoid unnecessary code.
10+
11+
For example, to exclude the video namespace from your sitemap, set `video: false` in your configuration:
12+
13+
```
14+
// astro.config.mjs
15+
import { sitemap } from '@astrojs/sitemap';
16+
17+
export default {
18+
integrations: [
19+
sitemap({
20+
namespaces: {
21+
video: false,
22+
// other namespaces remain enabled by default
23+
}
24+
})
25+
]
26+
};
27+
```
28+
29+
The generated XML will not include the `xmlns:video` namespace:
30+
31+
```
32+
<?xml version="1.0" encoding="UTF-8"?>
33+
<urlset
34+
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
35+
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
36+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
37+
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
38+
>
39+
<!-- ... -->
40+
</urlset>
41+
```

packages/integrations/sitemap/src/config-defaults.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ import type { SitemapOptions } from './index.js';
33
export const SITEMAP_CONFIG_DEFAULTS = {
44
filenameBase: 'sitemap',
55
entryLimit: 45000,
6+
namespaces: {
7+
news: true,
8+
xhtml: true,
9+
image: true,
10+
video: true,
11+
},
612
} satisfies SitemapOptions;

packages/integrations/sitemap/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ export type SitemapOptions =
3939
serialize?(item: SitemapItem): SitemapItem | Promise<SitemapItem | undefined> | undefined;
4040

4141
xslURL?: string;
42+
43+
// namespace configuration
44+
namespaces?: {
45+
news?: boolean;
46+
xhtml?: boolean;
47+
image?: boolean;
48+
video?: boolean;
49+
};
4250
}
4351
| undefined;
4452

@@ -180,6 +188,7 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
180188
customSitemaps,
181189
xslURL: xslURL,
182190
lastmod,
191+
namespaces: opts.namespaces,
183192
},
184193
config,
185194
);

packages/integrations/sitemap/src/schema.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ export const SitemapOptionsSchema = z
3737
changefreq: z.nativeEnum(ChangeFreq).optional(),
3838
lastmod: z.date().optional(),
3939
priority: z.number().min(0).max(1).optional(),
40+
41+
namespaces: z
42+
.object({
43+
news: z.boolean().optional(),
44+
xhtml: z.boolean().optional(),
45+
image: z.boolean().optional(),
46+
video: z.boolean().optional(),
47+
})
48+
.optional()
49+
.default(SITEMAP_CONFIG_DEFAULTS.namespaces),
4050
})
4151
.strict()
4252
.default(SITEMAP_CONFIG_DEFAULTS);

packages/integrations/sitemap/src/write-sitemap.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ type WriteSitemapConfig = {
1919
limit?: number;
2020
xslURL?: string;
2121
lastmod?: string;
22+
namespaces?: {
23+
news?: boolean;
24+
xhtml?: boolean;
25+
image?: boolean;
26+
video?: boolean;
27+
};
2228
};
2329

2430
// adapted from sitemap.js/sitemap-simple
@@ -34,6 +40,7 @@ export async function writeSitemap(
3440
publicBasePath = './',
3541
xslURL: xslUrl,
3642
lastmod,
43+
namespaces = { news: true, xhtml: true, image: true, video: true },
3744
}: WriteSitemapConfig,
3845
astroConfig: AstroConfig,
3946
) {
@@ -46,6 +53,13 @@ export async function writeSitemap(
4653
const sitemapStream = new SitemapStream({
4754
hostname,
4855
xslUrl,
56+
// Custom namespace handling
57+
xmlns: {
58+
news: namespaces?.news !== false,
59+
xhtml: namespaces?.xhtml !== false,
60+
image: namespaces?.image !== false,
61+
video: namespaces?.video !== false,
62+
},
4963
});
5064
const path = `./${filenameBase}-${i}.xml`;
5165
const writePath = resolve(destinationDir, path);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import assert from 'node:assert/strict';
2+
import { before, describe, it } from 'node:test';
3+
import { sitemap } from './fixtures/static/deps.mjs';
4+
import { loadFixture } from './test-utils.js';
5+
6+
describe('Namespaces Configuration', () => {
7+
let fixture;
8+
9+
describe('Default namespaces', () => {
10+
before(async () => {
11+
fixture = await loadFixture({
12+
root: './fixtures/static/',
13+
integrations: [sitemap()],
14+
});
15+
await fixture.build();
16+
});
17+
18+
it('includes all default namespaces', async () => {
19+
const xml = await fixture.readFile('/sitemap-0.xml');
20+
assert.ok(xml.includes('xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"'));
21+
assert.ok(xml.includes('xmlns:xhtml="http://www.w3.org/1999/xhtml"'));
22+
assert.ok(xml.includes('xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'));
23+
assert.ok(xml.includes('xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"'));
24+
});
25+
});
26+
27+
describe('Excluding news namespace', () => {
28+
before(async () => {
29+
fixture = await loadFixture({
30+
root: './fixtures/static/',
31+
integrations: [
32+
sitemap({
33+
namespaces: {
34+
news: false,
35+
},
36+
}),
37+
],
38+
});
39+
await fixture.build();
40+
});
41+
42+
it('excludes news namespace but includes others', async () => {
43+
const xml = await fixture.readFile('/sitemap-0.xml');
44+
assert.ok(!xml.includes('xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"'));
45+
assert.ok(xml.includes('xmlns:xhtml="http://www.w3.org/1999/xhtml"'));
46+
assert.ok(xml.includes('xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'));
47+
assert.ok(xml.includes('xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"'));
48+
});
49+
});
50+
51+
describe('Minimal namespaces', () => {
52+
before(async () => {
53+
fixture = await loadFixture({
54+
root: './fixtures/static/',
55+
integrations: [
56+
sitemap({
57+
namespaces: {
58+
news: false,
59+
xhtml: false,
60+
image: false,
61+
video: false,
62+
},
63+
}),
64+
],
65+
});
66+
await fixture.build();
67+
});
68+
69+
it('excludes all optional namespaces', async () => {
70+
const xml = await fixture.readFile('/sitemap-0.xml');
71+
assert.ok(!xml.includes('xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"'));
72+
assert.ok(!xml.includes('xmlns:xhtml="http://www.w3.org/1999/xhtml"'));
73+
assert.ok(!xml.includes('xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"'));
74+
assert.ok(!xml.includes('xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"'));
75+
// Still includes the main sitemap namespace
76+
assert.ok(xml.includes('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"'));
77+
});
78+
});
79+
});

0 commit comments

Comments
 (0)