Skip to content

Commit

Permalink
Add option to prefix sitemap (#9846)
Browse files Browse the repository at this point in the history
* Add option to prefix sitemap

* Fix call resolve twice

* let to const

* Apply suggestions from code review

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* change changeset patch to minor

* use node:test

* Update changeset

* Add regex validation for prefix

* Update .changeset/eighty-falcons-tease.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update prefix regex in SitemapOptionsSchema

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
3 people authored Feb 20, 2024
1 parent 3c73441 commit 9b78c99
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 9 deletions.
24 changes: 24 additions & 0 deletions .changeset/eighty-falcons-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@astrojs/sitemap": minor
---

Adds a new configuration option `prefix` that allows you to change the default `sitemap-*.xml` file name.

By default, running `astro build` creates both `sitemap-index.xml` and `sitemap-0.xml` in your output directory.

To change the names of these files (e.g. to `astrosite-index.xml` and `astrosite-0.xml`), set the `prefix` option in your `sitemap` integration configuration:

```
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [
sitemap({
prefix: 'astrosite-',
}),
],
});
```

This option is useful when Google Search Console is unable to fetch your default sitemap files, but can read renamed files.
31 changes: 22 additions & 9 deletions packages/integrations/sitemap/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { AstroConfig, AstroIntegration } from 'astro';
import path from 'node:path';
import path, { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { EnumChangefreq, LinkItem as LinkItemBase, SitemapItemLoose } from 'sitemap';
import { simpleSitemapAndIndex } from 'sitemap';
import { SitemapAndIndexStream, SitemapStream, streamToPromise } from 'sitemap';
import { ZodError } from 'zod';

import { generateSitemap } from './generate-sitemap.js';
import { validateOptions } from './validate-options.js';
import { createWriteStream } from 'node:fs';
import { Readable } from 'node:stream';

export { EnumChangefreq as ChangeFreqEnum } from 'sitemap';
export type ChangeFreq = `${EnumChangefreq}`;
Expand All @@ -33,6 +35,8 @@ export type SitemapOptions =
lastmod?: Date;
priority?: number;

prefix?: string;

// called for each sitemap item just before to save them on disk, sync or async
serialize?(item: SitemapItem): SitemapItem | Promise<SitemapItem | undefined> | undefined;
}
Expand All @@ -44,7 +48,6 @@ function formatConfigErrorMessage(err: ZodError) {
}

const PKG_NAME = '@astrojs/sitemap';
const OUTFILE = 'sitemap-index.xml';
const STATUS_CODE_PAGES = new Set(['404', '500']);

function isStatusCodePage(pathname: string): boolean {
Expand Down Expand Up @@ -77,7 +80,8 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {

const opts = validateOptions(config.site, options);

const { filter, customPages, serialize, entryLimit } = opts;
const { filter, customPages, serialize, entryLimit, prefix = 'sitemap-' } = opts;
const OUTFILE = `${prefix}index.xml`;

let finalSiteUrl: URL;
if (config.site) {
Expand Down Expand Up @@ -166,13 +170,22 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
}
}
const destDir = fileURLToPath(dir);
await simpleSitemapAndIndex({
hostname: finalSiteUrl.href,
destinationDir: destDir,
sourceData: urlData,

const sms = new SitemapAndIndexStream({
limit: entryLimit,
gzip: false,
getSitemapStream: (i) => {
const sitemapStream = new SitemapStream({ hostname: finalSiteUrl.href });
const fileName = `${prefix}${i}.xml`;

const ws = sitemapStream.pipe(createWriteStream(resolve(destDir + fileName)));

return [new URL(fileName, finalSiteUrl.href).toString(), sitemapStream, ws];
},
});

sms.pipe(createWriteStream(resolve(destDir + OUTFILE)));
await streamToPromise(Readable.from(urlData).pipe(sms));
sms.end();
logger.info(`\`${OUTFILE}\` created at \`${path.relative(process.cwd(), destDir)}\``);
} catch (err) {
if (err instanceof ZodError) {
Expand Down
7 changes: 7 additions & 0 deletions packages/integrations/sitemap/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export const SitemapOptionsSchema = z
changefreq: z.nativeEnum(ChangeFreq).optional(),
lastmod: z.date().optional(),
priority: z.number().min(0).max(1).optional(),

prefix: z
.string()
.regex(/^[a-zA-Z\-_]+$/gm, {
message: 'Only English alphabet symbols, hyphen and underscore allowed',
})
.optional(),
})
.strict()
.default(SITEMAP_CONFIG_DEFAULTS);
72 changes: 72 additions & 0 deletions packages/integrations/sitemap/test/prefix.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { loadFixture, readXML } from './test-utils.js';
import { sitemap } from './fixtures/static/deps.mjs';
import assert from 'node:assert/strict';
import { before, describe, it } from 'node:test';

describe('Prefix support', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;
const prefix = 'test-';

describe('Static', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/static/',
integrations: [
sitemap(),
sitemap({
prefix,
}),
],
});
await fixture.build();
});

it('Content is same', async () => {
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
const prefixData = await readXML(fixture.readFile(`/${prefix}0.xml`));
assert.deepEqual(prefixData, data);
});

it('Index file load correct sitemap', async () => {
const data = await readXML(fixture.readFile('/sitemap-index.xml'));
const sitemapUrl = data.sitemapindex.sitemap[0].loc[0];
assert.strictEqual(sitemapUrl, 'http://example.com/sitemap-0.xml');

const prefixData = await readXML(fixture.readFile(`/${prefix}index.xml`));
const prefixSitemapUrl = prefixData.sitemapindex.sitemap[0].loc[0];
assert.strictEqual(prefixSitemapUrl, `http://example.com/${prefix}0.xml`);
});
});

describe('SSR', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/ssr/',
integrations: [
sitemap(),
sitemap({
prefix,
}),
],
});
await fixture.build();
});

it('Content is same', async () => {
const data = await readXML(fixture.readFile('/client/sitemap-0.xml'));
const prefixData = await readXML(fixture.readFile(`/client/${prefix}0.xml`));
assert.deepEqual(prefixData, data);
});

it('Index file load correct sitemap', async () => {
const data = await readXML(fixture.readFile('/client/sitemap-index.xml'));
const sitemapUrl = data.sitemapindex.sitemap[0].loc[0];
assert.strictEqual(sitemapUrl, 'http://example.com/sitemap-0.xml');

const prefixData = await readXML(fixture.readFile(`/client/${prefix}index.xml`));
const prefixSitemapUrl = prefixData.sitemapindex.sitemap[0].loc[0];
assert.strictEqual(prefixSitemapUrl, `http://example.com/${prefix}0.xml`);
});
});
});

0 comments on commit 9b78c99

Please sign in to comment.