Skip to content

Commit 1b5a234

Browse files
Fix trailing-slash redirect body to match the Location header target (#18018)
Co-authored-by: factory[bot] <factory[bot]@users.noreply.github.com>
1 parent 558b301 commit 1b5a234

3 files changed

Lines changed: 46 additions & 3 deletions

File tree

‎.changeset/loud-months-sell.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes trailing-slash redirect response body pointing to the incoming URL instead of the redirect target. The `location` header was correct, but the HTML body (`<meta http-equiv="refresh">`, `<title>`, and `<a>` tag) contained the original request path without the trailing-slash correction or query string.

‎packages/astro/src/core/routing/trailing-slash-handler.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ export function handleTrailingSlash(state: FetchState): Response | undefined {
3030

3131
const addCookieHeader = state.renderOptions.addCookieHeader;
3232
const status = state.request.method === 'GET' ? 301 : 308;
33+
const location = redirect + url.search;
3334
const response = new Response(
3435
redirectTemplate({
3536
status,
36-
relativeLocation: url.pathname,
37-
absoluteLocation: redirect,
37+
relativeLocation: location,
38+
absoluteLocation: location,
3839
from: state.request.url,
3940
}),
4041
{
4142
status,
4243
headers: {
43-
location: redirect + url.search,
44+
location,
4445
},
4546
},
4647
);

‎packages/astro/test/units/fetch/index.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,43 @@ describe('trailingSlash()', () => {
256256
assert.equal(result.status, 301);
257257
assert.equal(result.headers.get('location'), '/about');
258258
});
259+
260+
it('includes the redirect target (not the incoming path) in the response body', async () => {
261+
const app = createTestApp([createPage(simplePage, { route: '/' })], {
262+
trailingSlash: 'always',
263+
});
264+
const request = stampApp(new Request('http://example.com/about'), app);
265+
const state = new FetchState(request);
266+
267+
const result = trailingSlash(state);
268+
assert.ok(result instanceof Response);
269+
const body = await result.text();
270+
assert.ok(
271+
body.includes('/about/'),
272+
'body should contain the redirect target with trailing slash',
273+
);
274+
assert.ok(
275+
!body.includes('url=/about"'),
276+
'meta refresh should not point to the incoming path without trailing slash',
277+
);
278+
});
279+
280+
it('preserves query string in the redirect response body', async () => {
281+
const app = createTestApp([createPage(simplePage, { route: '/' })], {
282+
trailingSlash: 'always',
283+
});
284+
const request = stampApp(new Request('http://example.com/about?ref=nav'), app);
285+
const state = new FetchState(request);
286+
287+
const result = trailingSlash(state);
288+
assert.ok(result instanceof Response);
289+
assert.equal(result.headers.get('location'), '/about/?ref=nav');
290+
const body = await result.text();
291+
assert.ok(
292+
body.includes('/about/?ref=nav'),
293+
'body should include the full redirect target with query string',
294+
);
295+
});
259296
});
260297

261298
// #endregion

0 commit comments

Comments
 (0)