@@ -21,7 +21,7 @@ import { ServerAssets } from '../assets';
21
21
import { Console } from '../console' ;
22
22
import { AngularAppManifest , getAngularAppManifest } from '../manifest' ;
23
23
import { AngularBootstrap , isNgModule } from '../utils/ng' ;
24
- import { joinUrlParts , stripLeadingSlash } from '../utils/url' ;
24
+ import { addTrailingSlash , joinUrlParts , stripLeadingSlash } from '../utils/url' ;
25
25
import {
26
26
PrerenderFallback ,
27
27
RenderMode ,
@@ -146,31 +146,36 @@ async function* traverseRoutesConfig(options: {
146
146
const metadata : ServerConfigRouteTreeNodeMetadata = {
147
147
renderMode : RenderMode . Prerender ,
148
148
...matchedMetaData ,
149
- route : currentRoutePath ,
149
+ // Match Angular router behavior
150
+ // ['one', 'two', ''] -> 'one/two/'
151
+ // ['one', 'two', 'three'] -> 'one/two/three'
152
+ route : path === '' ? addTrailingSlash ( currentRoutePath ) : currentRoutePath ,
150
153
} ;
151
154
152
155
delete metadata . presentInClientRouter ;
153
156
154
- // Handle redirects
155
- if ( typeof redirectTo === 'string' ) {
156
- const redirectToResolved = resolveRedirectTo ( currentRoutePath , redirectTo ) ;
157
+ if ( metadata . renderMode === RenderMode . Prerender ) {
158
+ // Handle SSG routes
159
+ yield * handleSSGRoute (
160
+ typeof redirectTo === 'string' ? redirectTo : undefined ,
161
+ metadata ,
162
+ parentInjector ,
163
+ invokeGetPrerenderParams ,
164
+ includePrerenderFallbackRoutes ,
165
+ ) ;
166
+ } else if ( typeof redirectTo === 'string' ) {
167
+ // Handle redirects
157
168
if ( metadata . status && ! VALID_REDIRECT_RESPONSE_CODES . has ( metadata . status ) ) {
158
169
yield {
159
170
error :
160
171
`The '${ metadata . status } ' status code is not a valid redirect response code. ` +
161
172
`Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
162
173
} ;
174
+
163
175
continue ;
164
176
}
165
- yield { ...metadata , redirectTo : redirectToResolved } ;
166
- } else if ( metadata . renderMode === RenderMode . Prerender ) {
167
- // Handle SSG routes
168
- yield * handleSSGRoute (
169
- metadata ,
170
- parentInjector ,
171
- invokeGetPrerenderParams ,
172
- includePrerenderFallbackRoutes ,
173
- ) ;
177
+
178
+ yield { ...metadata , redirectTo : resolveRedirectTo ( metadata . route , redirectTo ) } ;
174
179
} else {
175
180
yield metadata ;
176
181
}
@@ -214,13 +219,15 @@ async function* traverseRoutesConfig(options: {
214
219
* Handles SSG (Static Site Generation) routes by invoking `getPrerenderParams` and yielding
215
220
* all parameterized paths, returning any errors encountered.
216
221
*
222
+ * @param redirectTo - Optional path to redirect to, if specified.
217
223
* @param metadata - The metadata associated with the route tree node.
218
224
* @param parentInjector - The dependency injection container for the parent route.
219
225
* @param invokeGetPrerenderParams - A flag indicating whether to invoke the `getPrerenderParams` function.
220
226
* @param includePrerenderFallbackRoutes - A flag indicating whether to include fallback routes in the result.
221
227
* @returns An async iterable iterator that yields route tree node metadata for each SSG path or errors.
222
228
*/
223
229
async function * handleSSGRoute (
230
+ redirectTo : string | undefined ,
224
231
metadata : ServerConfigRouteTreeNodeMetadata ,
225
232
parentInjector : Injector ,
226
233
invokeGetPrerenderParams : boolean ,
@@ -239,6 +246,10 @@ async function* handleSSGRoute(
239
246
delete meta [ 'getPrerenderParams' ] ;
240
247
}
241
248
249
+ if ( redirectTo !== undefined ) {
250
+ meta . redirectTo = resolveRedirectTo ( currentRoutePath , redirectTo ) ;
251
+ }
252
+
242
253
if ( ! URL_PARAMETER_REGEXP . test ( currentRoutePath ) ) {
243
254
// Route has no parameters
244
255
yield {
@@ -279,7 +290,14 @@ async function* handleSSGRoute(
279
290
return value ;
280
291
} ) ;
281
292
282
- yield { ...meta , route : routeWithResolvedParams } ;
293
+ yield {
294
+ ...meta ,
295
+ route : routeWithResolvedParams ,
296
+ redirectTo :
297
+ redirectTo === undefined
298
+ ? undefined
299
+ : resolveRedirectTo ( routeWithResolvedParams , redirectTo ) ,
300
+ } ;
283
301
}
284
302
} catch ( error ) {
285
303
yield { error : `${ ( error as Error ) . message } ` } ;
@@ -319,7 +337,7 @@ function resolveRedirectTo(routePath: string, redirectTo: string): string {
319
337
}
320
338
321
339
// Resolve relative redirectTo based on the current route path.
322
- const segments = routePath . split ( '/' ) ;
340
+ const segments = routePath . replace ( URL_PARAMETER_REGEXP , '*' ) . split ( '/' ) ;
323
341
segments . pop ( ) ; // Remove the last segment to make it relative.
324
342
325
343
return joinUrlParts ( ...segments , redirectTo ) ;
@@ -459,7 +477,6 @@ export async function getRoutesFromAngularRouterConfig(
459
477
includePrerenderFallbackRoutes,
460
478
} ) ;
461
479
462
- let seenAppShellRoute : string | undefined ;
463
480
for await ( const result of traverseRoutes ) {
464
481
if ( 'error' in result ) {
465
482
errors . push ( result . error ) ;
@@ -549,8 +566,17 @@ export async function extractRoutesAndCreateRouteTree(
549
566
metadata . redirectTo = joinUrlParts ( baseHref , metadata . redirectTo ) ;
550
567
}
551
568
569
+ // Remove undefined fields
570
+ // Helps avoid unnecessary test updates
571
+ for ( const [ key , value ] of Object . entries ( metadata ) ) {
572
+ if ( value === undefined ) {
573
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
+ delete ( metadata as any ) [ key ] ;
575
+ }
576
+ }
577
+
552
578
const fullRoute = joinUrlParts ( baseHref , route ) ;
553
- routeTree . insert ( fullRoute , metadata ) ;
579
+ routeTree . insert ( fullRoute . replace ( URL_PARAMETER_REGEXP , '*' ) , metadata ) ;
554
580
}
555
581
556
582
return {
0 commit comments