Skip to content

Commit babf57f

Browse files
AhmadYasser1Princesseuhyanthomasdev
authored
feat(astro): Add fallbackRoutes to astro:routes:resolved's return type and include them in the sitemap integration (#15455)
* fix(sitemap): include i18n fallback pages in generated sitemap When using i18n with fallbackType: 'rewrite', fallback locale pages were generated as HTML files but excluded from the sitemap. This was caused by two filters that only matched type: 'page' and skipped type: 'fallback' routes. Fixes #14455 * feat: rework implementation * add missing full stop --------- Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com> Co-authored-by: Yan <61414485+yanthomasdev@users.noreply.github.com>
1 parent bc502ce commit babf57f

File tree

11 files changed

+123
-5
lines changed

11 files changed

+123
-5
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/sitemap': patch
3+
---
4+
5+
Fixes i18n fallback pages missing from the generated sitemap when using `fallbackType: 'rewrite'`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'astro': minor
3+
---
4+
5+
Adds `fallbackRoutes` to the `IntegrationResolvedRoute` type, exposing i18n fallback routes to integrations via the `astro:routes:resolved` hook for projects using `fallbackType: 'rewrite'`.
6+
7+
This allows integrations such as the sitemap integration to properly include generated fallback routes in their output.
8+
9+
```js
10+
{
11+
'astro:routes:resolved': ({ routes }) => {
12+
for (const route of routes) {
13+
for (const fallback of route.fallbackRoutes) {
14+
console.log(fallback.pathname) // e.g. /fr/about/
15+
}
16+
}
17+
}
18+
}
19+
```

packages/astro/src/integrations/hooks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,5 +703,8 @@ export function toIntegrationResolvedRoute(
703703
redirectRoute: route.redirectRoute
704704
? toIntegrationResolvedRoute(route.redirectRoute, trailingSlash)
705705
: undefined,
706+
fallbackRoutes: route.fallbackRoutes.map((fallbackRoute) =>
707+
toIntegrationResolvedRoute(fallbackRoute, trailingSlash),
708+
),
706709
};
707710
}

packages/astro/src/types/public/integrations.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ export interface IntegrationResolvedRoute
456456
*/
457457
redirectRoute?: IntegrationResolvedRoute;
458458

459+
/**
460+
* {@link RouteData.fallbackRoutes}
461+
*/
462+
fallbackRoutes: IntegrationResolvedRoute[];
463+
459464
/**
460465
* @param {any} data The optional parameters of the route
461466
*

packages/integrations/sitemap/src/index.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,12 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
128128
return new URL(fullPath, finalSiteUrl).href;
129129
});
130130

131-
const routeUrls = _routes.reduce<string[]>((urls, r) => {
132-
// Only expose pages, not endpoints or redirects
133-
if (r.type !== 'page') return urls;
134-
131+
const addRouteUrl = (urls: string[], r: IntegrationResolvedRoute): void => {
135132
/**
136133
* Dynamic URLs have entries with `undefined` pathnames
137134
*/
138135
if (r.pathname) {
139-
if (shouldIgnoreStatus(r.pathname ?? r.pattern)) return urls;
136+
if (shouldIgnoreStatus(r.pathname ?? r.pattern)) return;
140137

141138
// `finalSiteUrl` may end with a trailing slash
142139
// or not because of base paths.
@@ -154,6 +151,18 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => {
154151
urls.push(newUrl);
155152
}
156153
}
154+
};
155+
156+
const routeUrls = _routes.reduce<string[]>((urls, r) => {
157+
// Only expose pages, not endpoints or redirects
158+
if (r.type !== 'page') return urls;
159+
160+
addRouteUrl(urls, r);
161+
162+
// Include i18n fallback routes (e.g. /fr/ falling back to /en/)
163+
for (const fallbackRoute of (r.fallbackRoutes ?? [])) {
164+
addRouteUrl(urls, fallbackRoute);
165+
}
157166

158167
return urls;
159168
}, []);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import sitemap from '@astrojs/sitemap';
2+
import { defineConfig } from 'astro/config';
3+
4+
export default defineConfig({
5+
integrations: [sitemap()],
6+
site: 'http://example.com',
7+
i18n: {
8+
locales: ['en', 'fr'],
9+
defaultLocale: 'en',
10+
fallback: {
11+
fr: 'en',
12+
},
13+
routing: {
14+
prefixDefaultLocale: false,
15+
fallbackType: 'rewrite',
16+
},
17+
},
18+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@test/sitemap-i18n-fallback",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"astro": "workspace:*",
7+
"@astrojs/sitemap": "workspace:*"
8+
}
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
---
3+
<html>
4+
<head><title>About</title></head>
5+
<body><h1>About</h1></body>
6+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
---
3+
<html>
4+
<head><title>Home</title></head>
5+
<body><h1>Home</h1></body>
6+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import assert from 'node:assert/strict';
2+
import { before, describe, it } from 'node:test';
3+
import { loadFixture, readXML } from './test-utils.js';
4+
5+
describe('i18n fallback', () => {
6+
/** @type {import('./test-utils.js').Fixture} */
7+
let fixture;
8+
/** @type {string[]} */
9+
let urls;
10+
11+
before(async () => {
12+
fixture = await loadFixture({
13+
root: './fixtures/i18n-fallback/',
14+
});
15+
await fixture.build();
16+
const data = await readXML(fixture.readFile('/sitemap-0.xml'));
17+
urls = data.urlset.url.map((url) => url.loc[0]);
18+
});
19+
20+
it('includes default locale pages', async () => {
21+
assert.equal(urls.includes('http://example.com/'), true);
22+
assert.equal(urls.includes('http://example.com/about/'), true);
23+
});
24+
25+
it('includes fallback locale pages', async () => {
26+
assert.equal(urls.includes('http://example.com/fr/'), true);
27+
assert.equal(urls.includes('http://example.com/fr/about/'), true);
28+
});
29+
});

0 commit comments

Comments
 (0)