From 891e553ef71b864c7d6757977cdf11bd2d7514f7 Mon Sep 17 00:00:00 2001 From: Justrox Date: Wed, 26 Nov 2025 17:07:09 +0800 Subject: [PATCH 1/2] 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 --- packages/angular/ssr/src/routes/router.ts | 3 +- packages/angular/ssr/src/utils/url.ts | 33 +++++++++++++++++++++ packages/angular/ssr/test/utils/url_spec.ts | 21 +++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/angular/ssr/src/routes/router.ts b/packages/angular/ssr/src/routes/router.ts index f01e9989028e..6318c7d52b05 100644 --- a/packages/angular/ssr/src/routes/router.ts +++ b/packages/angular/ssr/src/routes/router.ts @@ -7,7 +7,7 @@ */ import { AngularAppManifest } from '../manifest'; -import { stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url'; +import { stripIndexHtmlFromURL, stripMatrixParams, stripAuxiliaryRoutes } from '../utils/url'; import { extractRoutesAndCreateRouteTree } from './ng-routes'; import { RouteTree, RouteTreeNodeMetadata } from './route-tree'; @@ -87,6 +87,7 @@ export class ServerRouter { // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`. let { pathname } = stripIndexHtmlFromURL(url); pathname = stripMatrixParams(pathname); + pathname = stripAuxiliaryRoutes(pathname); pathname = decodeURIComponent(pathname); return this.routeTree.match(pathname); diff --git a/packages/angular/ssr/src/utils/url.ts b/packages/angular/ssr/src/utils/url.ts index 1fa756e19c19..85fda319b7eb 100644 --- a/packages/angular/ssr/src/utils/url.ts +++ b/packages/angular/ssr/src/utils/url.ts @@ -235,3 +235,36 @@ export function stripMatrixParams(pathname: string): string { export function constructUrl(pathname: string, search: string, hash: string): string { return decodeURIComponent([stripTrailingSlash(pathname), search, hash].join('')); } + +/** + * Removes Angular auxiliary route segments from a given URL path. + * + * Auxiliary routes have the format `(outlet:segment)` appended to a primary URL. + * Multiple auxiliary routes and even nested forms will be removed entirely. + * + * @param pathname - The URL path from which to remove auxiliary route segments. + * @returns The cleaned URL path without auxiliary outlet routes. + * + * @example + * ```ts + * // Single auxiliary route + * stripAuxiliaryRoutes('/inbox/33(popup:compose)'); + * // → '/inbox/33' + * + * // Multiple auxiliary routes + * stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)'); + * // → '/mail/7' + * + * // Nested auxiliary routes (rare but possible) + * stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)'); + * // → '/inbox' + * + * // Path without auxiliary routes remains unchanged + * stripAuxiliaryRoutes('/path/to/resource'); + * // → '/path/to/resource' + * ``` + */ +export function stripAuxiliaryRoutes(pathname: string): string { + const index = pathname.indexOf('('); + return index !== -1 ? pathname.slice(0, index) : pathname; +} diff --git a/packages/angular/ssr/test/utils/url_spec.ts b/packages/angular/ssr/test/utils/url_spec.ts index 9a7a7cb3ad49..daeb0314dc0e 100644 --- a/packages/angular/ssr/test/utils/url_spec.ts +++ b/packages/angular/ssr/test/utils/url_spec.ts @@ -15,6 +15,7 @@ import { stripLeadingSlash, stripMatrixParams, stripTrailingSlash, + stripAuxiliaryRoutes, } from '../../src/utils/url'; describe('URL Utils', () => { @@ -208,4 +209,24 @@ describe('URL Utils', () => { expect(stripMatrixParams('')).toBe(''); }); }); + + describe('stripAuxiliaryRoutes', () => { + it('should remove a single auxiliary route', () => { + expect(stripAuxiliaryRoutes('/inbox/33(popup:compose)')).toBe('/inbox/33'); + }); + + it('should remove multiple auxiliary routes', () => { + expect(stripAuxiliaryRoutes('/mail/7(popup:compose)(drawer:details)(chat:room42)')).toBe( + '/mail/7', + ); + }); + + it('should remove nested auxiliary routes', () => { + expect(stripAuxiliaryRoutes('/inbox(overlay:view(popup:info))(tracker:read)')).toBe('/inbox'); + }); + + it('should not modify a path without auxiliary routes', () => { + expect(stripAuxiliaryRoutes('/path/to/resource')).toBe('/path/to/resource'); + }); + }); }); From db2543a6dd9c6672271856433546b2746cab5dc6 Mon Sep 17 00:00:00 2001 From: Justrox Date: Wed, 26 Nov 2025 19:51:31 +0800 Subject: [PATCH 2/2] fixup! fix(@angular/ssr): correctly handle auxiliary routes --- packages/angular/ssr/src/routes/router.ts | 2 +- packages/angular/ssr/src/utils/url.ts | 2 ++ packages/angular/ssr/test/utils/url_spec.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/angular/ssr/src/routes/router.ts b/packages/angular/ssr/src/routes/router.ts index 6318c7d52b05..ff2e7fecd991 100644 --- a/packages/angular/ssr/src/routes/router.ts +++ b/packages/angular/ssr/src/routes/router.ts @@ -7,7 +7,7 @@ */ import { AngularAppManifest } from '../manifest'; -import { stripIndexHtmlFromURL, stripMatrixParams, stripAuxiliaryRoutes } from '../utils/url'; +import { stripAuxiliaryRoutes, stripIndexHtmlFromURL, stripMatrixParams } from '../utils/url'; import { extractRoutesAndCreateRouteTree } from './ng-routes'; import { RouteTree, RouteTreeNodeMetadata } from './route-tree'; diff --git a/packages/angular/ssr/src/utils/url.ts b/packages/angular/ssr/src/utils/url.ts index 85fda319b7eb..db7fb6511ba7 100644 --- a/packages/angular/ssr/src/utils/url.ts +++ b/packages/angular/ssr/src/utils/url.ts @@ -264,7 +264,9 @@ export function constructUrl(pathname: string, search: string, hash: string): st * // → '/path/to/resource' * ``` */ + export function stripAuxiliaryRoutes(pathname: string): string { const index = pathname.indexOf('('); + return index !== -1 ? pathname.slice(0, index) : pathname; } diff --git a/packages/angular/ssr/test/utils/url_spec.ts b/packages/angular/ssr/test/utils/url_spec.ts index daeb0314dc0e..0e91d511c7a9 100644 --- a/packages/angular/ssr/test/utils/url_spec.ts +++ b/packages/angular/ssr/test/utils/url_spec.ts @@ -11,11 +11,11 @@ import { addTrailingSlash, buildPathWithParams, joinUrlParts, + stripAuxiliaryRoutes, stripIndexHtmlFromURL, stripLeadingSlash, stripMatrixParams, stripTrailingSlash, - stripAuxiliaryRoutes, } from '../../src/utils/url'; describe('URL Utils', () => {