Skip to content

Commit 6448f80

Browse files
alan-agius4jkrems
authored andcommitted
fix(@angular/ssr): prioritize the first matching route over subsequent ones
Ensures that the SSR router gives precedence to the first matching route, addressing the issue where later conflicting routes. This change prevents the incorrect prioritization of routes and ensures the intended route is matched first, aligning routing behavior. Closes: #29539
1 parent 62ee0f3 commit 6448f80

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

packages/angular/ssr/src/routes/ng-routes.ts

+15-7
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,6 @@ export async function getRoutesFromAngularRouterConfig(
603603
// Wait until the application is stable.
604604
await applicationRef.whenStable();
605605

606-
const routesResults: RouteTreeNodeMetadata[] = [];
607606
const errors: string[] = [];
608607

609608
let baseHref =
@@ -627,11 +626,12 @@ export async function getRoutesFromAngularRouterConfig(
627626
if (errors.length) {
628627
return {
629628
baseHref,
630-
routes: routesResults,
629+
routes: [],
631630
errors,
632631
};
633632
}
634633

634+
const routesResults: RouteTreeNodeMetadata[] = [];
635635
if (router.config.length) {
636636
// Retrieve all routes from the Angular router configuration.
637637
const traverseRoutes = traverseRoutesConfig({
@@ -645,11 +645,19 @@ export async function getRoutesFromAngularRouterConfig(
645645
entryPointToBrowserMapping,
646646
});
647647

648-
for await (const result of traverseRoutes) {
649-
if ('error' in result) {
650-
errors.push(result.error);
651-
} else {
652-
routesResults.push(result);
648+
const seenRoutes: Set<string> = new Set();
649+
for await (const routeMetadata of traverseRoutes) {
650+
if ('error' in routeMetadata) {
651+
errors.push(routeMetadata.error);
652+
continue;
653+
}
654+
655+
// If a result already exists for the exact same route, subsequent matches should be ignored.
656+
// This aligns with Angular's app router behavior, which prioritizes the first route.
657+
const routePath = routeMetadata.route;
658+
if (!seenRoutes.has(routePath)) {
659+
routesResults.push(routeMetadata);
660+
seenRoutes.add(routePath);
653661
}
654662
}
655663

packages/angular/ssr/test/routes/ng-routes_spec.ts

+34
Original file line numberDiff line numberDiff line change
@@ -636,4 +636,38 @@ describe('extractRoutesAndCreateRouteTree', () => {
636636
expect(errors).toHaveSize(0);
637637
expect(routeTree.toObject()).toHaveSize(2);
638638
});
639+
640+
it('should give precedence to the first matching route over subsequent ones', async () => {
641+
setAngularAppTestingManifest(
642+
[
643+
{
644+
path: '',
645+
children: [
646+
{ path: 'home', component: DummyComponent },
647+
{ path: '**', component: DummyComponent },
648+
],
649+
},
650+
// The following routes should be ignored due to Angular's routing behavior:
651+
// - ['', '**'] and ['**'] are equivalent, and the first match takes precedence.
652+
// - ['', 'home'] and ['home'] are equivalent, and the first match takes precedence.
653+
{
654+
path: 'home',
655+
redirectTo: 'never',
656+
},
657+
{
658+
path: '**',
659+
redirectTo: 'never',
660+
},
661+
],
662+
[{ path: '**', renderMode: RenderMode.Server }],
663+
);
664+
665+
const { routeTree, errors } = await extractRoutesAndCreateRouteTree({ url });
666+
expect(errors).toHaveSize(0);
667+
expect(routeTree.toObject()).toEqual([
668+
{ route: '/', renderMode: RenderMode.Server },
669+
{ route: '/home', renderMode: RenderMode.Server },
670+
{ route: '/**', renderMode: RenderMode.Server },
671+
]);
672+
});
639673
});

0 commit comments

Comments
 (0)