diff --git a/.changeset/fluffy-experts-tie.md b/.changeset/fluffy-experts-tie.md
new file mode 100644
index 000000000..52f9562c5
--- /dev/null
+++ b/.changeset/fluffy-experts-tie.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/sitemap': major
+---
+
+Sitemap only includes `page` routes (generated by `.astro` files) rather than all routes (pages, endpoints, or redirects). This behavior matches our existing documentation, but is a breaking change nonetheless.
diff --git a/.changeset/giant-tomatoes-dream.md b/.changeset/giant-tomatoes-dream.md
new file mode 100644
index 000000000..7a8ba593d
--- /dev/null
+++ b/.changeset/giant-tomatoes-dream.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/sitemap': patch
+---
+
+Ensure trailing slash is only added to page routes
diff --git a/packages/integrations/sitemap/README.md b/packages/integrations/sitemap/README.md
index 3e46bb7bb..e8b5a0128 100644
--- a/packages/integrations/sitemap/README.md
+++ b/packages/integrations/sitemap/README.md
@@ -1,6 +1,6 @@
# @astrojs/sitemap 🗺
-This **[Astro integration][astro-integration]** generates a sitemap based on your routes when you build your Astro project.
+This **[Astro integration][astro-integration]** generates a sitemap based on your pages when you build your Astro project.
- [Why Astro Sitemap](#why-astro-sitemap)
- [Installation](#installation)
@@ -337,7 +337,6 @@ The resulting sitemap looks like this:
## Examples
- The official Astro website uses Astro Sitemap to generate [its sitemap](https://astro.build/sitemap-index.xml).
-- The [integrations playground template](https://github.com/withastro/astro/tree/latest/examples/integrations-playground?on=github) comes with Astro Sitemap installed. Try adding a route and building the project!
- [Browse projects with Astro Sitemap on GitHub](https://github.com/search?q=%22@astrojs/sitemap%22+filename:package.json&type=Code) for more examples!
## Troubleshooting
diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts
index 0d52095c6..f22384b69 100644
--- a/packages/integrations/sitemap/src/index.ts
+++ b/packages/integrations/sitemap/src/index.ts
@@ -96,6 +96,9 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
});
let routeUrls = routes.reduce((urls, r) => {
+ // Only expose pages, not endpoints or redirects
+ if (r.type !== 'page') return urls;
+
/**
* Dynamic URLs have entries with `undefined` pathnames
*/
diff --git a/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs b/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs
index 9e142327e..e386f5e21 100644
--- a/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs
+++ b/packages/integrations/sitemap/test/fixtures/static/astro.config.mjs
@@ -4,4 +4,10 @@ import sitemap from '@astrojs/sitemap';
export default defineConfig({
integrations: [sitemap()],
site: 'http://example.com',
+ redirects: {
+ '/redirect': '/'
+ },
+ experimental: {
+ redirects: true
+ }
})
diff --git a/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts b/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts
new file mode 100644
index 000000000..8f9fa2c90
--- /dev/null
+++ b/packages/integrations/sitemap/test/fixtures/static/src/pages/endpoint.json.ts
@@ -0,0 +1,8 @@
+export async function get({}) {
+ return {
+ body: JSON.stringify({
+ name: 'Astro',
+ url: 'https://astro.build/',
+ }),
+ };
+}
diff --git a/packages/integrations/sitemap/test/routes.test.js b/packages/integrations/sitemap/test/routes.test.js
new file mode 100644
index 000000000..4b96e5987
--- /dev/null
+++ b/packages/integrations/sitemap/test/routes.test.js
@@ -0,0 +1,26 @@
+import { loadFixture, readXML } from './test-utils.js';
+import { expect } from 'chai';
+
+describe('routes', () => {
+ /** @type {import('./test-utils.js').Fixture} */
+ let fixture;
+ /** @type {string[]} */
+ let urls;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/static/',
+ });
+ await fixture.build();
+ const data = await readXML(fixture.readFile('/sitemap-0.xml'));
+ urls = data.urlset.url.map(url => url.loc[0]);
+ });
+
+ it('does not include endpoints', async () => {
+ expect(urls).to.not.include('http://example.com/endpoint.json');
+ });
+
+ it('does not include redirects', async () => {
+ expect(urls).to.not.include('http://example.com/redirect');
+ });
+});