diff --git a/.changeset/chatty-dolls-visit.md b/.changeset/chatty-dolls-visit.md new file mode 100644 index 000000000000..6b9e53e88e72 --- /dev/null +++ b/.changeset/chatty-dolls-visit.md @@ -0,0 +1,5 @@ +--- +'@astrojs/sitemap': minor +--- + +Adds support to SSR routes to sitemap generation. diff --git a/packages/integrations/sitemap/package.json b/packages/integrations/sitemap/package.json index afd959a62c92..7c465494ee39 100644 --- a/packages/integrations/sitemap/package.json +++ b/packages/integrations/sitemap/package.json @@ -37,6 +37,7 @@ "zod": "^3.17.3" }, "devDependencies": { + "@astrojs/node": "workspace:*", "astro": "workspace:*", "astro-scripts": "workspace:*", "chai": "^4.3.6", diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index e6e45ddd1dd6..0814ae0e1a3d 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -51,6 +51,8 @@ const OUTFILE = 'sitemap-index.xml'; const createPlugin = (options?: SitemapOptions): AstroIntegration => { let config: AstroConfig; + const logger = new Logger(PKG_NAME); + return { name: PKG_NAME, @@ -59,10 +61,15 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { config = cfg; }, - 'astro:build:done': async ({ dir, pages }) => { - const logger = new Logger(PKG_NAME); - + 'astro:build:done': async ({ dir, routes }) => { try { + if (!config.site) { + logger.warn( + 'The Sitemap integration requires the `site` astro.config option. Skipping.' + ); + return; + } + const opts = validateOptions(config.site, options); const { filter, customPages, serialize, entryLimit } = opts; @@ -78,12 +85,30 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { return; } - let pageUrls = pages.map((p) => { - if (p.pathname !== '' && !finalSiteUrl.pathname.endsWith('/')) - finalSiteUrl.pathname += '/'; - const path = finalSiteUrl.pathname + p.pathname; - return new URL(path, finalSiteUrl).href; - }); + let pageUrls = routes.reduce((urls, r) => { + /** + * Dynamic URLs have entries with `undefined` pathnames + */ + if (r.pathname) { + /** + * remove the initial slash from relative pathname + * because `finalSiteUrl` always has trailing slash + */ + const path = finalSiteUrl.pathname + r.generate(r.pathname).substring(1); + + let newUrl = new URL(path, finalSiteUrl).href; + + if (config.trailingSlash === 'never') { + urls.push(newUrl); + } else if (config.build.format === 'directory' && !newUrl.endsWith('/')) { + urls.push(newUrl + '/'); + } else { + urls.push(newUrl); + } + } + + return urls; + }, []); try { if (filter) { @@ -95,18 +120,11 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { } if (customPages) { - pageUrls = [...pageUrls, ...customPages]; + pageUrls = Array.from(new Set([...pageUrls, ...customPages])); } if (pageUrls.length === 0) { - // offer suggestion for SSR users - if (config.output !== 'static') { - logger.warn( - `No pages found! We can only detect sitemap routes for "static" builds. Since you are using an SSR adapter, we recommend manually listing your sitemap routes using the "customPages" integration option.\n\nExample: \`sitemap({ customPages: ['https://example.com/route'] })\`` - ); - } else { - logger.warn(`No pages found!\n\`${OUTFILE}\` not created.`); - } + logger.warn(`No pages found!\n\`${OUTFILE}\` not created.`); return; } diff --git a/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs new file mode 100644 index 000000000000..d914d4357e9f --- /dev/null +++ b/packages/integrations/sitemap/test/fixtures/ssr/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import sitemap from '@astrojs/sitemap'; +import nodeServer from '@astrojs/node' + +export default defineConfig({ + integrations: [sitemap()], + site: 'http://example.com', + output: 'server', + adapter: nodeServer({ + mode: "standalone" + }) +}) diff --git a/packages/integrations/sitemap/test/fixtures/ssr/package.json b/packages/integrations/sitemap/test/fixtures/ssr/package.json new file mode 100644 index 000000000000..980e02e73898 --- /dev/null +++ b/packages/integrations/sitemap/test/fixtures/ssr/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/sitemap-trailing-slash", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/sitemap": "workspace:*" + } +} diff --git a/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro new file mode 100644 index 000000000000..0c7fb90a735e --- /dev/null +++ b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/one.astro @@ -0,0 +1,8 @@ + + + One + + +

One

+ + diff --git a/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro new file mode 100644 index 000000000000..e7ba9910e2a6 --- /dev/null +++ b/packages/integrations/sitemap/test/fixtures/ssr/src/pages/two.astro @@ -0,0 +1,8 @@ + + + Two + + +

Two

+ + diff --git a/packages/integrations/sitemap/test/ssr.test.js b/packages/integrations/sitemap/test/ssr.test.js new file mode 100644 index 000000000000..e6f8412d56fc --- /dev/null +++ b/packages/integrations/sitemap/test/ssr.test.js @@ -0,0 +1,22 @@ +import { loadFixture, readXML } from './test-utils.js'; +import { expect } from 'chai'; + +describe('SSR support', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/ssr/', + }); + await fixture.build(); + }); + + it('SSR pages require zero config', async () => { + const data = await readXML(fixture.readFile('/client/sitemap-0.xml')); + const urls = data.urlset.url; + + expect(urls[0].loc[0]).to.equal('http://example.com/one/'); + expect(urls[1].loc[0]).to.equal('http://example.com/two/'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49ad594ee4e3..c770aa864c5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4505,6 +4505,9 @@ importers: specifier: ^3.17.3 version: 3.20.6 devDependencies: + '@astrojs/node': + specifier: workspace:* + version: link:../node astro: specifier: workspace:* version: link:../../astro @@ -4521,6 +4524,15 @@ importers: specifier: 0.5.0 version: 0.5.0 + packages/integrations/sitemap/test/fixtures/ssr: + dependencies: + '@astrojs/sitemap': + specifier: workspace:* + version: link:../../.. + astro: + specifier: workspace:* + version: link:../../../../../astro + packages/integrations/sitemap/test/fixtures/trailing-slash: dependencies: '@astrojs/sitemap':