@@ -178,12 +178,22 @@ async function* handleRoute(options: {
178178
179179 const parentRoutes = new Map < string , RouteTreeNodeMetadata > ( ) ;
180180
181+ if ( typeof redirectTo === 'string' ) {
182+ let routeSegments : string [ ] | undefined ;
183+ // Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
184+ metadata . redirectTo = resolveRedirectTo ( parentRoute , route , redirectTo , ( parameter ) => {
185+ routeSegments ??= joinUrlParts ( baseHref , currentRoutePath ) . split ( '/' ) ;
186+
187+ return `*${ routeSegments . lastIndexOf ( parameter ) } ` ;
188+ } ) ;
189+ metadata . relativeRedirect = redirectTo [ 0 ] !== '/' ? true : undefined ;
190+ }
191+
181192 if ( metadata . renderMode === RenderMode . Prerender ) {
182193 for await ( const result of handleSSGRoute (
183194 serverConfigRouteTree ,
184195 route ,
185196 parentRoute ,
186- baseHref ,
187197 metadata ,
188198 parentInjector ,
189199 invokeGetPrerenderParams ,
@@ -195,31 +205,16 @@ async function* handleRoute(options: {
195205 parentRoutes . set ( result . route , result ) ;
196206 }
197207 }
198- } else if ( redirectTo !== undefined ) {
199- if ( metadata . status && ! isValidRedirectResponseCode ( metadata . status ) ) {
200- yield {
201- error :
202- `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
203- `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
204- } ;
205- } else if ( typeof redirectTo === 'string' ) {
206- // Reference the route parameters by their position in the URL (see `buildPathWithParams`).
207- const routeSegments = joinUrlParts ( baseHref , currentRoutePath ) . split ( '/' ) ;
208- parentRoutes . set ( metadata . route , {
209- ...metadata ,
210- redirectTo : resolveRedirectTo (
211- parentRoute ,
212- replaceRedirectParams (
213- route ,
214- redirectTo ,
215- ( parameter ) => `*${ routeSegments . lastIndexOf ( parameter ) } ` ,
216- ) ,
217- ) ,
218- relativeRedirect : redirectTo [ 0 ] !== '/' ? true : undefined ,
219- } ) ;
220- } else {
221- parentRoutes . set ( metadata . route , metadata ) ;
222- }
208+ } else if (
209+ redirectTo !== undefined &&
210+ metadata . status &&
211+ ! isValidRedirectResponseCode ( metadata . status )
212+ ) {
213+ yield {
214+ error :
215+ `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
216+ `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
217+ } ;
223218 } else {
224219 parentRoutes . set ( metadata . route , metadata ) ;
225220 }
@@ -436,7 +431,6 @@ function appendPreloadToMetadata(
436431 * @param serverConfigRouteTree - The tree representing the server's routing setup.
437432 * @param route - The route configuration.
438433 * @param parentRoute - The path of the parent route.
439- * @param baseHref - The base href of the application.
440434 * @param metadata - The metadata associated with the route tree node.
441435 * @param parentInjector - The dependency injection container for the parent route.
442436 * @param invokeGetPrerenderParams - A flag indicating whether to invoke the `getPrerenderParams` function.
@@ -447,7 +441,6 @@ async function* handleSSGRoute(
447441 serverConfigRouteTree : RouteTree < ServerConfigRouteTreeAdditionalMetadata > | undefined ,
448442 route : Route ,
449443 parentRoute : string ,
450- baseHref : string ,
451444 metadata : ServerConfigRouteTreeNodeMetadata ,
452445 parentInjector : Injector ,
453446 invokeGetPrerenderParams : boolean ,
@@ -466,21 +459,6 @@ async function* handleSSGRoute(
466459 delete meta [ 'getPrerenderParams' ] ;
467460 }
468461
469- const redirectTo = typeof route . redirectTo === 'string' ? route . redirectTo : undefined ;
470- if ( redirectTo !== undefined ) {
471- // Reference the route parameters by their position in the URL (see `buildPathWithParams`).
472- const routeSegments = joinUrlParts ( baseHref , currentRoutePath ) . split ( '/' ) ;
473- meta . redirectTo = resolveRedirectTo (
474- parentRoute ,
475- replaceRedirectParams (
476- route ,
477- redirectTo ,
478- ( parameter ) => `*${ routeSegments . lastIndexOf ( parameter ) } ` ,
479- ) ,
480- ) ;
481- meta . relativeRedirect = redirectTo [ 0 ] !== '/' ? true : undefined ;
482- }
483-
484462 const isCatchAllRoute = CATCH_ALL_REGEXP . test ( currentRoutePath ) ;
485463 if (
486464 ( isCatchAllRoute && ! getPrerenderParams ) ||
@@ -522,6 +500,7 @@ async function* handleSSGRoute(
522500 }
523501 }
524502
503+ const redirectTo = typeof route . redirectTo === 'string' ? route . redirectTo : undefined ;
525504 const parameters = await runInInjectionContext ( parentInjector , ( ) => getPrerenderParams ( ) ) ;
526505 try {
527506 for ( const params of parameters ) {
@@ -538,11 +517,9 @@ async function* handleSSGRoute(
538517 ? undefined
539518 : resolveRedirectTo (
540519 parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , replacer ) ,
541- replaceRedirectParams (
542- route ,
543- redirectTo ,
544- ( parameter ) => params [ parameter . slice ( 1 ) ] ,
545- ) ,
520+ route ,
521+ redirectTo ,
522+ ( parameter ) => params [ parameter . slice ( 1 ) ] ,
546523 ) ,
547524 } ;
548525 }
@@ -595,69 +572,55 @@ function handlePrerenderParamsReplacement(
595572}
596573
597574/**
598- * Resolves the `redirectTo` property for a given route.
575+ * Resolves the `redirectTo` property for a given route, replacing any `:id` route parameters
576+ * in its path and resolving relative paths against `parentRoute`.
599577 *
600- * This function processes the `redirectTo` property to ensure that it correctly
601- * resolves relative to the parent route path. If `redirectTo` is an absolute path,
602- * it is returned as is. If it is a relative path, it is resolved based on the parent route path,
603- * and, like in the Angular router, its query and fragment are ignored.
578+ * Like in the Angular router, only whole path segments starting with `:` are parameters, only
579+ * parameters defined in the path of the redirecting route itself can be used, and the query and
580+ * fragment of relative redirects are ignored.
604581 *
605582 * @param parentRoute - The path of the parent route.
606- * @param redirectTo - The target path for redirection.
607- * @returns The resolved redirect path as a string.
608- */
609- function resolveRedirectTo ( parentRoute : string , redirectTo : string ) : string {
610- if ( redirectTo [ 0 ] === '/' ) {
611- // If the redirectTo path is absolute, return it as is.
612- return redirectTo ;
613- }
614-
615- // Match Angular router behavior: replace all segments matched by the route.
616- const [ path ] = redirectTo . split ( / [ ? # ] / , 1 ) ;
617-
618- return joinUrlParts ( parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , '*' ) , path ) ;
619- }
620-
621- /**
622- * Replaces the route parameters, such as `:id`, in the path of `redirectTo`.
623- *
624- * Like in the Angular router, only whole path segments starting with `:` are parameters, and only
625- * the parameters defined in the path of the redirecting route itself can be used.
626- *
627583 * @param route - The redirecting route.
628584 * @param redirectTo - The target path for redirection.
629- * @param resolve - A function that returns the replacement for a parameter, such as `:id`.
630- * @returns The redirect path with its route parameters replaced .
585+ * @param resolveParam - A function that returns the replacement for a parameter, such as `:id`.
586+ * @returns The resolved redirect path as a string .
631587 * @throws If `redirectTo` uses a parameter that is not defined in the path of the route.
632588 */
633- function replaceRedirectParams (
589+ function resolveRedirectTo (
590+ parentRoute : string ,
634591 route : Route ,
635592 redirectTo : string ,
636- resolve : ( parameter : string ) => string ,
593+ resolveParam : ( parameter : string ) => string ,
637594) : string {
638- if ( route . matcher ) {
639- // The parameters of a custom matcher are only known at runtime.
595+ if ( redirectTo [ 0 ] === '/' && ( route . matcher || ! redirectTo . includes ( '/:' ) ) ) {
640596 return redirectTo ;
641597 }
642598
643- const routeSegments = route . path ?. split ( '/' ) ?? [ ] ;
644599 const [ path ] = redirectTo . split ( / [ ? # ] / , 1 ) ;
645- const resolvedPath = path
646- . split ( '/' )
647- . map ( ( segment ) => {
648- if ( segment [ 0 ] !== ':' ) {
649- return segment ;
650- }
600+ let resolvedPath = path ;
601+
602+ // The parameters of a custom matcher are only known at runtime.
603+ if ( ! route . matcher && path . includes ( ':' ) ) {
604+ const routeSegments = route . path ?. split ( '/' ) ?? [ ] ;
605+ resolvedPath = path
606+ . split ( '/' )
607+ . map ( ( segment ) => {
608+ if ( segment [ 0 ] !== ':' ) {
609+ return segment ;
610+ }
651611
652- if ( ! routeSegments . includes ( segment ) ) {
653- throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find '${ segment } '.` ) ;
654- }
612+ if ( ! routeSegments . includes ( segment ) ) {
613+ throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find '${ segment } '.` ) ;
614+ }
655615
656- return resolve ( segment ) ;
657- } )
658- . join ( '/' ) ;
616+ return resolveParam ( segment ) ;
617+ } )
618+ . join ( '/' ) ;
619+ }
659620
660- return resolvedPath + redirectTo . slice ( path . length ) ;
621+ return redirectTo [ 0 ] === '/'
622+ ? resolvedPath + redirectTo . slice ( path . length )
623+ : joinUrlParts ( parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , '*' ) , resolvedPath ) ;
661624}
662625
663626/**
0 commit comments