Skip to content

Commit 891e553

Browse files
committed
fix(@angular/ssr): correctly handle auxiliary routes
This commit updates Angular SSR URL preprocessing to remove auxiliary outlet route segments before route matching. Previously, URLs containing auxiliary routes appended with outlet syntax, such as `/path(foo:bar)`, could prevent correct route resolution. A new `stripAuxiliaryRoutes` utility function has been added to strip these segments, ensuring that SSR route matching behaves consistently with client-side routing. This issue is similar to #31457, and the fix is based from #31476
1 parent 212ebde commit 891e553

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

packages/angular/ssr/src/routes/router.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { AngularAppManifest } from '../manifest';
10-
import { stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url';
10+
import { stripIndexHtmlFromURL, stripMatrixParams, stripAuxiliaryRoutes } from '../utils/url';
1111
import { extractRoutesAndCreateRouteTree } from './ng-routes';
1212
import { RouteTree, RouteTreeNodeMetadata } from './route-tree';
1313

@@ -87,6 +87,7 @@ export class ServerRouter {
8787
// A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
8888
let { pathname } = stripIndexHtmlFromURL(url);
8989
pathname = stripMatrixParams(pathname);
90+
pathname = stripAuxiliaryRoutes(pathname);
9091
pathname = decodeURIComponent(pathname);
9192

9293
return this.routeTree.match(pathname);

packages/angular/ssr/src/utils/url.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,36 @@ export function stripMatrixParams(pathname: string): string {
235235
export function constructUrl(pathname: string, search: string, hash: string): string {
236236
return decodeURIComponent([stripTrailingSlash(pathname), search, hash].join(''));
237237
}
238+
239+
/**
240+
* Removes Angular auxiliary route segments from a given URL path.
241+
*
242+
* Auxiliary routes have the format `(outlet:segment)` appended to a primary URL.
243+
* Multiple auxiliary routes and even nested forms will be removed entirely.
244+
*
245+
* @param pathname - The URL path from which to remove auxiliary route segments.
246+
* @returns The cleaned URL path without auxiliary outlet routes.
247+
*
248+
* @example
249+
* ```ts
250+
* // Single auxiliary route
251+
* stripAuxiliaryRoutes('/inbox/33(popup:compose)');
252+
* // → '/inbox/33'
253+
*
254+
* // Multiple auxiliary routes
255+
* stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)');
256+
* // → '/mail/7'
257+
*
258+
* // Nested auxiliary routes (rare but possible)
259+
* stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)');
260+
* // → '/inbox'
261+
*
262+
* // Path without auxiliary routes remains unchanged
263+
* stripAuxiliaryRoutes('/path/to/resource');
264+
* // → '/path/to/resource'
265+
* ```
266+
*/
267+
export function stripAuxiliaryRoutes(pathname: string): string {
268+
const index = pathname.indexOf('(');
269+
return index !== -1 ? pathname.slice(0, index) : pathname;
270+
}

packages/angular/ssr/test/utils/url_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
stripLeadingSlash,
1616
stripMatrixParams,
1717
stripTrailingSlash,
18+
stripAuxiliaryRoutes,
1819
} from '../../src/utils/url';
1920

2021
describe('URL Utils', () => {
@@ -208,4 +209,24 @@ describe('URL Utils', () => {
208209
expect(stripMatrixParams('')).toBe('');
209210
});
210211
});
212+
213+
describe('stripAuxiliaryRoutes', () => {
214+
it('should remove a single auxiliary route', () => {
215+
expect(stripAuxiliaryRoutes('/inbox/33(popup:compose)')).toBe('/inbox/33');
216+
});
217+
218+
it('should remove multiple auxiliary routes', () => {
219+
expect(stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)')).toBe(
220+
'/mail/7',
221+
);
222+
});
223+
224+
it('should remove nested auxiliary routes', () => {
225+
expect(stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)')).toBe('/inbox');
226+
});
227+
228+
it('should not modify a path without auxiliary routes', () => {
229+
expect(stripAuxiliaryRoutes('/path/to/resource')).toBe('/path/to/resource');
230+
});
231+
});
211232
});

0 commit comments

Comments
 (0)