diff --git a/.changeset/tricky-windows-destroy.md b/.changeset/tricky-windows-destroy.md
new file mode 100644
index 000000000..a0870ecbb
--- /dev/null
+++ b/.changeset/tricky-windows-destroy.md
@@ -0,0 +1,5 @@
+---
+'@faustwp/core': patch
+---
+
+Fix incorrect sitemap URLs when using sitemapIndexPath
diff --git a/packages/faustwp-core/src/server/sitemaps/createSitemaps.ts b/packages/faustwp-core/src/server/sitemaps/createSitemaps.ts
index 2fd92187b..8553fb57a 100644
--- a/packages/faustwp-core/src/server/sitemaps/createSitemaps.ts
+++ b/packages/faustwp-core/src/server/sitemaps/createSitemaps.ts
@@ -62,8 +62,7 @@ export async function createRootSitemapIndex(
throw new Error('Request object must have URL');
}
- // get sitemapIndexPath config param
- // fetch sitemap from WP
+ // Get the trimmed sitemap index path
const trimmedWpUrl = trim(getWpUrl(), '/');
const trimmedFrontendUrl = trim(frontendUrl, '/');
const trimmedSitemapIndexPath = trim(
@@ -75,10 +74,10 @@ export async function createRootSitemapIndex(
let sitemaps: SitemapSchemaSitemapElement[] = [];
if (!isUndefined(pages) && isArray(pages) && pages.length) {
- const trimmedFaustPagesPart = `${trim(
- SITEMAP_INDEX_PATH,
+ const trimmedFaustPagesPart = `${trimmedSitemapIndexPath}?sitemap=${trim(
+ FAUST_PAGES_PATHNAME,
'/',
- )}?sitemap=${trim(FAUST_PAGES_PATHNAME, '/')}`;
+ )}`;
const sitemapFaustPagesUrl = `${trimmedFrontendUrl}/${trimmedFaustPagesPart}`;
sitemaps = [
@@ -170,11 +169,11 @@ export async function createRootSitemapIndex(
*
* @example
* Replaces http://headless.local/wp-sitemap-posts-page-1.xml with
- * http://localhost:3000/wp-sitemap-posts-page-1.xml
+ * http://localhost:3000/sitemap_index.xml?sitemap=wp-sitemap-posts-page-1.xml
*/
wpSitemaps.forEach((sitemap) => {
const url = new URL(sitemap.loc);
- const sitemapUrl = `${trim(frontendUrl, '/')}/sitemap.xml?sitemap=${trim(
+ const sitemapUrl = `${trimmedFrontendUrl}/${trimmedSitemapIndexPath}?sitemap=${trim(
url.pathname,
'/',
)}`;
@@ -293,11 +292,11 @@ export async function handleSitemapPath(
let urls: SitemapSchemaUrlElement[] = [];
/**
- * Replace the existing WordPress URL in each "loc" with the headless URL
+ * Replace the existing WordPress URL in each "loc" with the frontend URL
*
* @example
- * Replaces http://headless.local/wp-sitemap-posts-page-1.xml with
- * http://localhost:3000/wp-sitemap-posts-page-1.xml
+ * Replaces http://headless.local/page-1/ with
+ * http://localhost:3000/page-1/
*/
wpSitemapUrls.forEach((url) => {
urls = [
diff --git a/packages/faustwp-core/tests/server/sitemaps/createSitemaps.test.ts b/packages/faustwp-core/tests/server/sitemaps/createSitemaps.test.ts
index 1827cff8e..70206f2bd 100644
--- a/packages/faustwp-core/tests/server/sitemaps/createSitemaps.test.ts
+++ b/packages/faustwp-core/tests/server/sitemaps/createSitemaps.test.ts
@@ -15,18 +15,17 @@ describe('createRootSitemapIndex', () => {
process.env = envBackup;
});
- const invalidSitemapIndexXML = `
+ const invalidSitemapIndexXML = `
`;
- const validSitemapIndex1RecordXML = `
+ const validSitemapIndex1RecordXML = `
http://headless.local/post-sitemap.xml
-
- `;
+ `;
- const validSitemapIndexXML = `
+ const validSitemapIndexXML = `
http://headless.local/post-sitemap.xml
@@ -41,8 +40,7 @@ describe('createRootSitemapIndex', () => {
http://headless.local/author-sitemap.xml
2021-12-17T16:56:55+00:00
-
- `;
+ `;
it('returns undefined when the response to the WP sitemap is not ok', async () => {
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
@@ -86,10 +84,7 @@ describe('createRootSitemapIndex', () => {
});
it('ignores sitemaps that match the sitemapPathsToIgnore property', async () => {
- const createSitemapIndexSpy = jest.spyOn(
- sitemapUtils,
- 'createSitemapIndex',
- );
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
@@ -123,10 +118,7 @@ describe('createRootSitemapIndex', () => {
});
it('ignores sitemaps that match a sitemapPathsToIgnore wildcard', async () => {
- const createSitemapIndexSpy = jest.spyOn(
- sitemapUtils,
- 'createSitemapIndex',
- );
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
@@ -160,10 +152,7 @@ describe('createRootSitemapIndex', () => {
});
it('returns the proper sitemap urls with replacing URLs', async () => {
- const createSitemapIndexSpy = jest.spyOn(
- sitemapUtils,
- 'createSitemapIndex',
- );
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
@@ -211,10 +200,7 @@ describe('createRootSitemapIndex', () => {
* configured properly. Ensure it returns an array of URLs
*/
it('returns the proper sitemap urls with only 1 record', async () => {
- const createSitemapIndexSpy = jest.spyOn(
- sitemapUtils,
- 'createSitemapIndex',
- );
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
jest.spyOn(global, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
ok: true,
@@ -242,6 +228,130 @@ describe('createRootSitemapIndex', () => {
expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
});
+
+ it('uses the default sitemapIndexPath when none is provided', async () => {
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
+ jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
+ expect(url).toBe('http://headless.local/sitemap.xml');
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => Promise.resolve(validSitemapIndexXML),
+ }) as Promise;
+ });
+
+ const req = {
+ url: 'http://localhost:3000/sitemap.xml',
+ } as NextRequest;
+
+ const config: GetSitemapPropsConfig = {
+ frontendUrl: 'http://localhost:3000',
+ };
+
+ await createSitemaps.createRootSitemapIndex(req, config);
+
+ const expectedSitemaps = [
+ {
+ loc: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap.xml?sitemap=page-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap.xml?sitemap=category-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap.xml?sitemap=author-sitemap.xml',
+ lastmod: '2021-12-17T16:56:55+00:00',
+ },
+ ];
+
+ expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
+ });
+
+ it('uses the custom sitemapIndexPath when provided', async () => {
+ jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
+ expect(url).toBe('http://headless.local/custom-sitemap-index.xml');
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => Promise.resolve(validSitemapIndexXML),
+ }) as Promise;
+ });
+
+ const req = {
+ url: 'http://localhost:3000/custom-sitemap-index.xml',
+ } as NextRequest;
+
+ const config: GetSitemapPropsConfig = {
+ frontendUrl: 'http://localhost:3000',
+ sitemapIndexPath: '/custom-sitemap-index.xml',
+ };
+
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
+
+ await createSitemaps.createRootSitemapIndex(req, config);
+
+ const expectedSitemaps = [
+ {
+ loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=post-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=page-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=category-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/custom-sitemap-index.xml?sitemap=author-sitemap.xml',
+ lastmod: '2021-12-17T16:56:55+00:00',
+ },
+ ];
+
+ expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
+ });
+
+ it('constructs correct child sitemap URLs with custom sitemapIndexPath', async () => {
+ jest.spyOn(global, 'fetch').mockImplementationOnce((url) => {
+ expect(url).toBe('http://headless.local/sitemap_index.xml');
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => Promise.resolve(validSitemapIndexXML),
+ }) as Promise;
+ });
+
+ const req = {
+ url: 'http://localhost:3000/sitemap_index.xml',
+ } as NextRequest;
+
+ const config: GetSitemapPropsConfig = {
+ frontendUrl: 'http://localhost:3000',
+ sitemapIndexPath: '/sitemap_index.xml',
+ };
+
+ const createSitemapIndexSpy = jest.spyOn(sitemapUtils, 'createSitemapIndex');
+
+ await createSitemaps.createRootSitemapIndex(req, config);
+
+ const expectedSitemaps = [
+ {
+ loc: 'http://localhost:3000/sitemap_index.xml?sitemap=post-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap_index.xml?sitemap=page-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap_index.xml?sitemap=category-sitemap.xml',
+ },
+ {
+ loc: 'http://localhost:3000/sitemap_index.xml?sitemap=author-sitemap.xml',
+ lastmod: '2021-12-17T16:56:55+00:00',
+ },
+ ];
+
+ expect(createSitemapIndexSpy).toHaveBeenCalledWith(expectedSitemaps);
+ });
});
describe('createPagesSitemap()', () => {
@@ -259,7 +369,7 @@ describe('createPagesSitemap()', () => {
frontendUrl: 'http://localhost:3000',
};
- let req = {
+ const req = {
url: 'http://localhost:3000/sitemap-faust-pages.xml',
} as NextRequest;
@@ -310,7 +420,7 @@ describe('createPagesSitemap()', () => {
const createSitemapSpy = jest.spyOn(sitemapUtils, 'createSitemap');
- let req = {
+ const req = {
url: 'http://localhost:3000/sitemap-faust-pages.xml',
} as NextRequest;
@@ -330,8 +440,8 @@ describe('handleSitemapPath()', () => {
afterAll(() => {
process.env = envBackup;
});
- const validSitemapXML = `
-
+ const validSitemapXML = `
+
http://headless.local/
@@ -347,19 +457,16 @@ describe('handleSitemapPath()', () => {
weekly
0.5
+ `;
-
- `;
-
- const validSitemap1RecordXML = `
-
+ const validSitemap1RecordXML = `
+
http://headless.local/about
-
- `;
+ `;
- const invalidSitemapXML = `
+ const invalidSitemapXML = `
`;
it('returns undefined when the response to the WP sitemap is not ok', async () => {
@@ -371,7 +478,7 @@ describe('handleSitemapPath()', () => {
});
const req = {
- url: 'http://localhost:3000/sitemap-posts.xml',
+ url: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
} as NextRequest;
const config: GetSitemapPropsConfig = {
@@ -392,7 +499,7 @@ describe('handleSitemapPath()', () => {
});
const req = {
- url: 'http://localhost:3000/sitemap-posts.xml',
+ url: 'http://localhost:3000/sitemap.xml?sitemap=post-sitemap.xml',
} as NextRequest;
const config: GetSitemapPropsConfig = {
@@ -415,7 +522,7 @@ describe('handleSitemapPath()', () => {
});
const req = {
- url: 'http://localhost:5000/sitemap-pages.xml',
+ url: 'http://localhost:5000/sitemap.xml?sitemap=page-sitemap.xml',
} as NextRequest;
const config: GetSitemapPropsConfig = {
@@ -461,7 +568,7 @@ describe('handleSitemapPath()', () => {
});
const req = {
- url: 'http://localhost:3000/sitemap-pages.xml',
+ url: 'http://localhost:3000/sitemap.xml?sitemap=page-sitemap.xml',
} as NextRequest;
const config: GetSitemapPropsConfig = {
diff --git a/packages/faustwp-core/tests/server/sitemaps/getSitemapProps.test.ts b/packages/faustwp-core/tests/server/sitemaps/getSitemapProps.test.ts
index 0f148e13e..7ee6a1d29 100644
--- a/packages/faustwp-core/tests/server/sitemaps/getSitemapProps.test.ts
+++ b/packages/faustwp-core/tests/server/sitemaps/getSitemapProps.test.ts
@@ -105,4 +105,35 @@ describe('validateConfig', () => {
'The pages path property must be a string',
);
});
+
+ it('throws an error if sitemapIndexPath is not a string', () => {
+ const config: any = {
+ frontendUrl: 'http://localhost:3000',
+ sitemapIndexPath: 123,
+ };
+
+ expect(() => getSitemapProps.validateConfig(config)).toThrow(
+ 'sitemapIndexPath must be a string',
+ );
+ });
+
+ it('throws an error if sitemapIndexPath does not start with a forward slash', () => {
+ const config: any = {
+ frontendUrl: 'http://localhost:3000',
+ sitemapIndexPath: 'sitemap_index.xml',
+ };
+
+ expect(() => getSitemapProps.validateConfig(config)).toThrow(
+ 'sitemapIndexPath must start with a forward slash',
+ );
+ });
+
+ it('passes validation when sitemapIndexPath is a valid string', () => {
+ const config: any = {
+ frontendUrl: 'http://localhost:3000',
+ sitemapIndexPath: '/sitemap_index.xml',
+ };
+
+ expect(() => getSitemapProps.validateConfig(config)).not.toThrow();
+ });
});