fix: strip build-time basePath from router.push redirects after login #390

This commit is contained in:
Linus Rath
2026-06-12 00:23:00 +02:00
parent e1c28e767a
commit 1e63e2469a
4 changed files with 59 additions and 8 deletions
+30
View File
@@ -72,3 +72,33 @@ describe('withBasePath — asset-URL fallbacks under a subpath', () => {
expect(getPathPrefix()).toBe('/webmail');
});
});
describe('toRouterPath — router.push paths under a subpath', () => {
// With a build-time basePath, Next's router prepends the prefix itself, so
// browser-derived paths (redirect_after_login stores
// window.location.pathname) must be stripped or the redirect lands on
// /webmail/webmail/en. See #390.
it('strips the static base path from a stored browser path', async () => {
const { toRouterPath } = await loadNav('/webmail');
expect(toRouterPath('/webmail/en')).toBe('/en');
expect(toRouterPath('/webmail/en/calendar?view=day')).toBe('/en/calendar?view=day');
expect(toRouterPath('/webmail')).toBe('/');
expect(toRouterPath('/webmail?compose=1')).toBe('/?compose=1');
});
it('leaves already-stripped and unrelated paths alone', async () => {
const { toRouterPath } = await loadNav('/webmail');
expect(toRouterPath('/en')).toBe('/en');
expect(toRouterPath('/')).toBe('/');
// Shares the prefix text but is a different first segment.
expect(toRouterPath('/webmail2/en')).toBe('/webmail2/en');
});
it('passes paths through unchanged when no base path is built in', async () => {
// Legacy runtime-detected proxy mounts: Next knows nothing about the
// prefix, so router.push needs the full prefixed path.
const { toRouterPath } = await loadNav(undefined);
expect(toRouterPath('/webmail/en')).toBe('/webmail/en');
expect(toRouterPath('/en')).toBe('/en');
});
});
+21
View File
@@ -96,6 +96,27 @@ export function withBasePath(url: string | null | undefined): string {
}
/**
* Converts a browser-style path (as found in `window.location.pathname`,
* which always includes the mount prefix) into a path safe to hand to Next's
* client router (`router.push` / `router.replace`).
*
* When the app is built with NEXT_PUBLIC_BASE_PATH, Next's router prepends
* the basePath itself, so a stored prefixed path would get it twice (#390) —
* strip it here. Legacy runtime-detected proxy mounts pass through unchanged:
* Next knows nothing about that prefix, so the router needs the full path.
*
* Accepts paths with query/hash suffixes (`/webmail/en/calendar?view=day`).
*/
export function toRouterPath(path: string): string {
if (!STATIC_BASE_PATH || !path.startsWith(STATIC_BASE_PATH)) return path;
const rest = path.slice(STATIC_BASE_PATH.length);
if (rest === '') return '/';
if (rest[0] === '/') return rest;
if (rest[0] === '?' || rest[0] === '#') return '/' + rest;
return path; // different first segment that merely shares the prefix text
}
/**
* Extracts the locale from the current URL, skipping any mount prefix.
* Falls back to 'en' when no known locale segment is found.