@@ -21,7 +21,7 @@ import { ServerAssets } from '../assets';
2121import { Console } from '../console' ;
2222import { AngularAppManifest , getAngularAppManifest } from '../manifest' ;
2323import { AngularBootstrap , isNgModule } from '../utils/ng' ;
24- import { joinUrlParts , stripLeadingSlash } from '../utils/url' ;
24+ import { addTrailingSlash , joinUrlParts , stripLeadingSlash } from '../utils/url' ;
2525import {
2626 PrerenderFallback ,
2727 RenderMode ,
@@ -146,31 +146,36 @@ async function* traverseRoutesConfig(options: {
146146 const metadata : ServerConfigRouteTreeNodeMetadata = {
147147 renderMode : RenderMode . Prerender ,
148148 ...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 ,
150153 } ;
151154
152155 delete metadata . presentInClientRouter ;
153156
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
157168 if ( metadata . status && ! VALID_REDIRECT_RESPONSE_CODES . has ( metadata . status ) ) {
158169 yield {
159170 error :
160171 `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
161172 `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
162173 } ;
174+
163175 continue ;
164176 }
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 ) } ;
174179 } else {
175180 yield metadata ;
176181 }
@@ -214,13 +219,15 @@ async function* traverseRoutesConfig(options: {
214219 * Handles SSG (Static Site Generation) routes by invoking `getPrerenderParams` and yielding
215220 * all parameterized paths, returning any errors encountered.
216221 *
222+ * @param redirectTo - Optional path to redirect to, if specified.
217223 * @param metadata - The metadata associated with the route tree node.
218224 * @param parentInjector - The dependency injection container for the parent route.
219225 * @param invokeGetPrerenderParams - A flag indicating whether to invoke the `getPrerenderParams` function.
220226 * @param includePrerenderFallbackRoutes - A flag indicating whether to include fallback routes in the result.
221227 * @returns An async iterable iterator that yields route tree node metadata for each SSG path or errors.
222228 */
223229async function * handleSSGRoute (
230+ redirectTo : string | undefined ,
224231 metadata : ServerConfigRouteTreeNodeMetadata ,
225232 parentInjector : Injector ,
226233 invokeGetPrerenderParams : boolean ,
@@ -239,6 +246,10 @@ async function* handleSSGRoute(
239246 delete meta [ 'getPrerenderParams' ] ;
240247 }
241248
249+ if ( redirectTo !== undefined ) {
250+ meta . redirectTo = resolveRedirectTo ( currentRoutePath , redirectTo ) ;
251+ }
252+
242253 if ( ! URL_PARAMETER_REGEXP . test ( currentRoutePath ) ) {
243254 // Route has no parameters
244255 yield {
@@ -279,7 +290,14 @@ async function* handleSSGRoute(
279290 return value ;
280291 } ) ;
281292
282- yield { ...meta , route : routeWithResolvedParams } ;
293+ yield {
294+ ...meta ,
295+ route : routeWithResolvedParams ,
296+ redirectTo :
297+ redirectTo === undefined
298+ ? undefined
299+ : resolveRedirectTo ( routeWithResolvedParams , redirectTo ) ,
300+ } ;
283301 }
284302 } catch ( error ) {
285303 yield { error : `${ ( error as Error ) . message } ` } ;
@@ -319,7 +337,7 @@ function resolveRedirectTo(routePath: string, redirectTo: string): string {
319337 }
320338
321339 // Resolve relative redirectTo based on the current route path.
322- const segments = routePath . split ( '/' ) ;
340+ const segments = routePath . replace ( URL_PARAMETER_REGEXP , '*' ) . split ( '/' ) ;
323341 segments . pop ( ) ; // Remove the last segment to make it relative.
324342
325343 return joinUrlParts ( ...segments , redirectTo ) ;
@@ -459,7 +477,6 @@ export async function getRoutesFromAngularRouterConfig(
459477 includePrerenderFallbackRoutes,
460478 } ) ;
461479
462- let seenAppShellRoute : string | undefined ;
463480 for await ( const result of traverseRoutes ) {
464481 if ( 'error' in result ) {
465482 errors . push ( result . error ) ;
@@ -549,8 +566,17 @@ export async function extractRoutesAndCreateRouteTree(
549566 metadata . redirectTo = joinUrlParts ( baseHref , metadata . redirectTo ) ;
550567 }
551568
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+
552578 const fullRoute = joinUrlParts ( baseHref , route ) ;
553- routeTree . insert ( fullRoute , metadata ) ;
579+ routeTree . insert ( fullRoute . replace ( URL_PARAMETER_REGEXP , '*' ) , metadata ) ;
554580 }
555581
556582 return {
0 commit comments