Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(routing): handle base path with trailing slash 'never' configuration #13598

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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/puny-masks-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix routing with base paths when trailingSlash is set to 'never'. This ensures requests to '/base' are correctly matched when the base path is set to '/base', without requiring a trailing slash.
39 changes: 28 additions & 11 deletions packages/astro/src/core/routing/rewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,45 @@ export function findRouteToRewrite({
}

let pathname = newUrl.pathname;

const shouldAppendSlash = shouldAppendForwardSlash(trailingSlash, buildFormat);

// Special handling for base path
if (base !== '/') {
// Check if this is a request to the base path
const isBasePathRequest = newUrl.pathname === base ||
newUrl.pathname === removeTrailingForwardSlash(base);

if (base !== '/' && newUrl.pathname.startsWith(base)) {
pathname = shouldAppendSlash
? appendForwardSlash(newUrl.pathname)
: removeTrailingForwardSlash(newUrl.pathname);
pathname = pathname.slice(base.length);
if (isBasePathRequest) {
// For root path requests at the base URL
// When trailingSlash is 'never', we should match '' (empty string pathname)
// When trailingSlash is 'always', we should match '/' pathname
pathname = shouldAppendSlash ? '/' : '';
} else if (newUrl.pathname.startsWith(base)) {
// For non-root paths under the base
pathname = shouldAppendSlash
? appendForwardSlash(newUrl.pathname)
: removeTrailingForwardSlash(newUrl.pathname);
pathname = pathname.slice(base.length);
}
}

// Ensure pathname starts with '/' when needed
if (!pathname.startsWith('/') && shouldAppendSlash && newUrl.pathname.endsWith('/')) {
// when base is in the rewrite call and trailingSlash is 'always' this is needed or it will 404.
pathname = prependForwardSlash(pathname);
}

if (pathname === '/' && base !== '/' && !shouldAppendSlash) {
// when rewriting to index and trailingSlash is 'never' this is needed or it will 404
// in this case the pattern will look for '/^$/' so '/' will never match
// Convert '/' to '' for trailingSlash: 'never'
if (pathname === '/' && !shouldAppendSlash) {
pathname = '';
}

newUrl.pathname = joinPaths(...[base, pathname].filter(Boolean));
// Set the final URL pathname
if (base !== '/' && (pathname === '' || pathname === '/') && !shouldAppendSlash) {
// Special case for root path at base URL with trailingSlash: 'never'
newUrl.pathname = removeTrailingForwardSlash(base);
} else {
newUrl.pathname = joinPaths(...[base, pathname].filter(Boolean));
}

const decodedPathname = decodeURI(pathname);
let foundRoute;
Expand Down
63 changes: 60 additions & 3 deletions packages/astro/test/units/routing/trailing-slash.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ const fileSystem = {
};

describe('trailingSlash', () => {
let fixture;
let container;
let settings;
let baseContainer;

before(async () => {
const fixture = await createFixture(fileSystem);
settings = await createBasicSettings({
fixture = await createFixture(fileSystem);

// Create the first container with trailingSlash: 'always'
const settings = await createBasicSettings({
root: fixture.path,
trailingSlash: 'always',
output: 'server',
Expand All @@ -47,12 +50,44 @@ describe('trailingSlash', () => {
settings,
logger: defaultLogger,
});

// Create the second container with base path and trailingSlash: 'never'
const baseSettings = await createBasicSettings({
root: fixture.path,
trailingSlash: 'never',
base: 'base',
output: 'server',
adapter: testAdapter(),
integrations: [
{
name: 'test',
hooks: {
'astro:config:setup': ({ injectRoute }) => {
injectRoute({
pattern: '/',
entrypoint: './src/pages/api.ts',
});
injectRoute({
pattern: '/injected',
entrypoint: './src/pages/api.ts',
});
},
},
},
],
});
baseContainer = await createContainer({
settings: baseSettings,
logger: defaultLogger,
});
});

after(async () => {
await container.close();
await baseContainer.close();
});

// Tests for trailingSlash: 'always'
it('should match the API route when request has a trailing slash', async () => {
const { req, res, text } = createRequestAndResponse({
method: 'GET',
Expand Down Expand Up @@ -124,4 +159,26 @@ describe('trailingSlash', () => {
const json = await text();
assert.equal(json, '{"success":true}');
});

// Tests for trailingSlash: 'never' with base path
it('should not have trailing slash on root path when base is set and trailingSlash is never', async () => {
const { req, res, text } = createRequestAndResponse({
method: 'GET',
url: '/base',
});
baseContainer.handle(req, res);
const json = await text();
assert.equal(json, '{"success":true}');
});

it('should not match root path with trailing slash when base is set and trailingSlash is never', async () => {
const { req, res, text } = createRequestAndResponse({
method: 'GET',
url: '/base/',
});
baseContainer.handle(req, res);
const html = await text();
assert.equal(html.includes(`<span class="statusMessage">Not found</span>`), true);
assert.equal(res.statusCode, 404);
});
});
Loading