Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thin-memes-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/underscore-redirects': patch
---

Fixes redirect file generation to respect trailingSlash config
Comment thread
alexanderniebuhr marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ describe(
it('Creates a redirects file', async () => {
const redirects = await fixture.readFile('./_redirects');
const parts = redirects.split(/\s+/);
assert.deepEqual(parts, ['', '/other', '/', '301', '']);
// Snapshots are not supported in Node.js test yet (https://github.com/nodejs/node/issues/48260)
assert.equal(redirects, '\n/other / 301\n');
// based on https://github.com/withastro/astro/issues/16030 for the default option `trailingSlash: 'ignore'` both variants should be generated
assert.deepEqual(parts, ['', '/other/', '/', '301', '/other', '/', '301', '']);
});

it('Does not create .html files', async () => {
Expand Down
11 changes: 7 additions & 4 deletions packages/integrations/netlify/test/static/redirects.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@ describe('SSG - Redirects', () => {
it('Creates a redirects file', async () => {
const redirects = await fixture.readFile('./_redirects');
const parts = redirects.split(/\s+/);
// based on https://github.com/withastro/astro/issues/16030 for the default option `trailingSlash: 'ignore'` both variants should be generated
assert.deepEqual(parts, [
'',

'/two/',
'/',
'302',
'/two',
'/',
'302',

'/other/',
'/',
'301',
'/other',
'/',
'301',

'/blog/*',
'/team/articles/*/index.html',
'301',

'',
]);
});
Expand Down
52 changes: 44 additions & 8 deletions packages/underscore-redirects/src/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function getRedirectStatus(route: IntegrationResolvedRoute): ValidRedirectStatus
}

interface CreateRedirectsFromAstroRoutesParams {
config: Pick<AstroConfig, 'build' | 'output' | 'base'>;
config: Pick<AstroConfig, 'build' | 'output' | 'base' | 'trailingSlash'>;
/**
* Maps a `RouteData` to a dynamic target
*/
Expand All @@ -27,6 +27,35 @@ interface CreateRedirectsFromAstroRoutesParams {
assets: HookParameters<'astro:build:done'>['assets'];
}

/**
* Returns the path(s) to use for a redirect entry based on the trailingSlash config.
* - 'always': ensures the path ends with '/'
* - 'never': ensures the path does not end with '/'
* - 'ignore'(default): returns both with and without trailing slash variants
*/
function getTrailingSlashPaths(
Comment thread
alexanderniebuhr marked this conversation as resolved.
Outdated
inputPath: string,
trailingSlash: 'always' | 'never' | 'ignore',
): string[] {
if (inputPath === '/') {
return ['/'];
}

const hasTrailingSlash = inputPath.endsWith('/');
const withoutSlash = hasTrailingSlash ? inputPath.slice(0, -1) : inputPath;
const withSlash = hasTrailingSlash ? inputPath : inputPath + '/';

switch (trailingSlash) {
case 'always':
return [withSlash];
case 'never':
return [withoutSlash];
case 'ignore':
default:
return [withoutSlash, withSlash];
}
}

/**
* Takes a set of routes and creates a Redirects object from them.
*/
Expand Down Expand Up @@ -57,13 +86,20 @@ export function createRedirectsFromAstroRoutes({
// Use `entrypoint` when available to keep trailing slashes in _redirects.
const inputPath =
route.type === 'redirect' && route.entrypoint ? route.entrypoint : route.pathname;
redirects.add({
dynamic: false,
input: `${base}${inputPath}`,
target: typeof route.redirect === 'object' ? route.redirect.destination : route.redirect,
status: getRedirectStatus(route),
weight: 2,
});

// Generate redirect entries based on trailingSlash config.
const trailingSlash = config.trailingSlash ?? 'ignore';
const paths = getTrailingSlashPaths(inputPath, trailingSlash);
for (const path of paths) {
redirects.add({
dynamic: false,
input: `${base}${path}`,
target:
typeof route.redirect === 'object' ? route.redirect.destination : route.redirect,
status: getRedirectStatus(route),
weight: 2,
});
}
continue;
}

Expand Down
Loading