From 9bacf3981995626cf935cf1620c391338de1c9df Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 22 Jan 2025 07:59:06 +0000 Subject: [PATCH] fix(@angular/ssr): properly manage catch-all routes with base href This fix ensures that catch-all routes (e.g., wildcard routes like `**`) are handled correctly when a base href is configured in an Angular SSR application. Closes #29397 (cherry picked from commit 3546c6d12d4c4ea97c7699145b051e5114c79cb4) --- packages/angular/ssr/src/routes/ng-routes.ts | 4 ++-- packages/angular/ssr/src/routes/route-tree.ts | 18 +++++++----------- .../angular/ssr/test/routes/route-tree_spec.ts | 10 +++++++++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/angular/ssr/src/routes/ng-routes.ts b/packages/angular/ssr/src/routes/ng-routes.ts index 770cb3d6c7ce..b6554610dd9a 100644 --- a/packages/angular/ssr/src/routes/ng-routes.ts +++ b/packages/angular/ssr/src/routes/ng-routes.ts @@ -590,13 +590,13 @@ export async function getRoutesFromAngularRouterConfig( if (serverConfigRouteTree) { for (const { route, presentInClientRouter } of serverConfigRouteTree.traverse()) { - if (presentInClientRouter || route === '**') { + if (presentInClientRouter || route.endsWith('/**')) { // Skip if matched or it's the catch-all route. continue; } errors.push( - `The '${route}' server route does not match any routes defined in the Angular ` + + `The '${stripLeadingSlash(route)}' server route does not match any routes defined in the Angular ` + `routing configuration (typically provided as a part of the 'provideRouter' call). ` + 'Please make sure that the mentioned server route is present in the Angular routing configuration.', ); diff --git a/packages/angular/ssr/src/routes/route-tree.ts b/packages/angular/ssr/src/routes/route-tree.ts index ba79688aa3c6..7ae0a4a84bca 100644 --- a/packages/angular/ssr/src/routes/route-tree.ts +++ b/packages/angular/ssr/src/routes/route-tree.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { stripTrailingSlash } from '../utils/url'; +import { addLeadingSlash } from '../utils/url'; import { RenderMode } from './route-config'; /** @@ -116,7 +116,7 @@ export class RouteTree = {}> * The root node of the route tree. * All routes are stored and accessed relative to this root node. */ - private readonly root = this.createEmptyRouteTreeNode(''); + private readonly root = this.createEmptyRouteTreeNode(''); /** * A counter that tracks the order of route insertion. @@ -155,7 +155,7 @@ export class RouteTree = {}> // At the leaf node, store the full route and its associated metadata node.metadata = { ...metadata, - route: normalizedSegments.join('/'), + route: addLeadingSlash(normalizedSegments.join('/')), }; node.insertionIndex = this.insertionIndexCounter++; @@ -230,7 +230,7 @@ export class RouteTree = {}> * @returns An array of path segments. */ private getPathSegments(route: string): string[] { - return stripTrailingSlash(route).split('/'); + return route.split('/').filter(Boolean); } /** @@ -246,18 +246,14 @@ export class RouteTree = {}> * @returns The node that best matches the remaining segments or `undefined` if no match is found. */ private traverseBySegments( - remainingSegments: string[] | undefined, + remainingSegments: string[], node = this.root, ): RouteTreeNode | undefined { const { metadata, children } = node; // If there are no remaining segments and the node has metadata, return this node - if (!remainingSegments?.length) { - if (metadata) { - return node; - } - - return; + if (!remainingSegments.length) { + return metadata ? node : node.children.get('**'); } // If the node has no children, end the traversal diff --git a/packages/angular/ssr/test/routes/route-tree_spec.ts b/packages/angular/ssr/test/routes/route-tree_spec.ts index fe8097a16c83..e800edf51792 100644 --- a/packages/angular/ssr/test/routes/route-tree_spec.ts +++ b/packages/angular/ssr/test/routes/route-tree_spec.ts @@ -150,7 +150,7 @@ describe('RouteTree', () => { describe('match', () => { it('should handle empty routes', () => { routeTree.insert('', { renderMode: RenderMode.Server }); - expect(routeTree.match('')).toEqual({ route: '', renderMode: RenderMode.Server }); + expect(routeTree.match('')).toEqual({ route: '/', renderMode: RenderMode.Server }); }); it('should insert and match basic routes', () => { @@ -274,5 +274,13 @@ describe('RouteTree', () => { renderMode: RenderMode.Server, }); }); + + it('should correctly match catch-all segments with a prefix', () => { + routeTree.insert('/de/**', { renderMode: RenderMode.Server }); + expect(routeTree.match('/de')).toEqual({ + route: '/de/**', + renderMode: RenderMode.Server, + }); + }); }); });