@@ -178,12 +178,21 @@ async function* handleRoute(options: {
178178
179179 const parentRoutes = new Map < string , RouteTreeNodeMetadata > ( ) ;
180180
181+ if ( typeof redirectTo === 'string' ) {
182+ // Reference the route parameters by their position in the URL (see `buildUrlWithParams`).
183+ metadata . redirectTo = resolveRedirectTo ( parentRoute , route , redirectTo , ( parameter ) => {
184+ const routeSegments = joinUrlParts ( baseHref , currentRoutePath ) . split ( '/' ) ;
185+
186+ return `*${ routeSegments . lastIndexOf ( parameter ) } ` ;
187+ } ) ;
188+ metadata . relativeRedirect = redirectTo [ 0 ] !== '/' ? true : undefined ;
189+ }
190+
181191 if ( metadata . renderMode === RenderMode . Prerender ) {
182192 for await ( const result of handleSSGRoute (
183193 serverConfigRouteTree ,
184194 route ,
185195 parentRoute ,
186- baseHref ,
187196 metadata ,
188197 parentInjector ,
189198 invokeGetPrerenderParams ,
@@ -195,31 +204,16 @@ async function* handleRoute(options: {
195204 parentRoutes . set ( result . route , result ) ;
196205 }
197206 }
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- }
207+ } else if (
208+ redirectTo !== undefined &&
209+ metadata . status &&
210+ ! isValidRedirectResponseCode ( metadata . status )
211+ ) {
212+ yield {
213+ error :
214+ `The '${ metadata . status } ' status code is not a valid redirect response code. ` +
215+ `Please use one of the following redirect response codes: ${ [ ...VALID_REDIRECT_RESPONSE_CODES . values ( ) ] . join ( ', ' ) } .` ,
216+ } ;
223217 } else {
224218 parentRoutes . set ( metadata . route , metadata ) ;
225219 }
@@ -436,7 +430,6 @@ function appendPreloadToMetadata(
436430 * @param serverConfigRouteTree - The tree representing the server's routing setup.
437431 * @param route - The route configuration.
438432 * @param parentRoute - The path of the parent route.
439- * @param baseHref - The base href of the application.
440433 * @param metadata - The metadata associated with the route tree node.
441434 * @param parentInjector - The dependency injection container for the parent route.
442435 * @param invokeGetPrerenderParams - A flag indicating whether to invoke the `getPrerenderParams` function.
@@ -447,7 +440,6 @@ async function* handleSSGRoute(
447440 serverConfigRouteTree : RouteTree < ServerConfigRouteTreeAdditionalMetadata > | undefined ,
448441 route : Route ,
449442 parentRoute : string ,
450- baseHref : string ,
451443 metadata : ServerConfigRouteTreeNodeMetadata ,
452444 parentInjector : Injector ,
453445 invokeGetPrerenderParams : boolean ,
@@ -466,21 +458,6 @@ async function* handleSSGRoute(
466458 delete meta [ 'getPrerenderParams' ] ;
467459 }
468460
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-
484461 const isCatchAllRoute = CATCH_ALL_REGEXP . test ( currentRoutePath ) ;
485462 if (
486463 ( isCatchAllRoute && ! getPrerenderParams ) ||
@@ -522,6 +499,7 @@ async function* handleSSGRoute(
522499 }
523500 }
524501
502+ const redirectTo = typeof route . redirectTo === 'string' ? route . redirectTo : undefined ;
525503 const parameters = await runInInjectionContext ( parentInjector , ( ) => getPrerenderParams ( ) ) ;
526504 try {
527505 for ( const params of parameters ) {
@@ -538,11 +516,9 @@ async function* handleSSGRoute(
538516 ? undefined
539517 : resolveRedirectTo (
540518 parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , replacer ) ,
541- replaceRedirectParams (
542- route ,
543- redirectTo ,
544- ( parameter ) => params [ parameter . slice ( 1 ) ] ,
545- ) ,
519+ route ,
520+ redirectTo ,
521+ ( parameter ) => params [ parameter . slice ( 1 ) ] ,
546522 ) ,
547523 } ;
548524 }
@@ -595,69 +571,55 @@ function handlePrerenderParamsReplacement(
595571}
596572
597573/**
598- * Resolves the `redirectTo` property for a given route.
574+ * Resolves the `redirectTo` property for a given route, replacing any `:id` route parameters
575+ * in its path and resolving relative paths against `parentRoute`.
599576 *
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.
577+ * Like in the Angular router, only whole path segments starting with `:` are parameters, only
578+ * parameters defined in the path of the redirecting route itself can be used, and the query and
579+ * fragment of relative redirects are ignored.
604580 *
605581 * @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- *
627582 * @param route - The redirecting route.
628583 * @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 .
584+ * @param resolveParam - A function that returns the replacement for a parameter, such as `:id`.
585+ * @returns The resolved redirect path as a string .
631586 * @throws If `redirectTo` uses a parameter that is not defined in the path of the route.
632587 */
633- function replaceRedirectParams (
588+ function resolveRedirectTo (
589+ parentRoute : string ,
634590 route : Route ,
635591 redirectTo : string ,
636- resolve : ( parameter : string ) => string ,
592+ resolveParam : ( parameter : string ) => string ,
637593) : string {
638- if ( route . matcher ) {
639- // The parameters of a custom matcher are only known at runtime.
594+ if ( redirectTo [ 0 ] === '/' && ( route . matcher || ! redirectTo . includes ( '/:' ) ) ) {
640595 return redirectTo ;
641596 }
642597
643- const routeSegments = route . path ?. split ( '/' ) ?? [ ] ;
644598 const [ path ] = redirectTo . split ( / [ ? # ] / , 1 ) ;
645- const resolvedPath = path
646- . split ( '/' )
647- . map ( ( segment ) => {
648- if ( segment [ 0 ] !== ':' ) {
649- return segment ;
650- }
599+ let resolvedPath = path ;
600+
601+ // The parameters of a custom matcher are only known at runtime.
602+ if ( ! route . matcher && path . includes ( ':' ) ) {
603+ const routeSegments = route . path ?. split ( '/' ) ?? [ ] ;
604+ resolvedPath = path
605+ . split ( '/' )
606+ . map ( ( segment ) => {
607+ if ( segment [ 0 ] !== ':' ) {
608+ return segment ;
609+ }
651610
652- if ( ! routeSegments . includes ( segment ) ) {
653- throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find '${ segment } '.` ) ;
654- }
611+ if ( ! routeSegments . includes ( segment ) ) {
612+ throw new Error ( `Cannot redirect to '${ redirectTo } '. Cannot find '${ segment } '.` ) ;
613+ }
655614
656- return resolve ( segment ) ;
657- } )
658- . join ( '/' ) ;
615+ return resolveParam ( segment ) ;
616+ } )
617+ . join ( '/' ) ;
618+ }
659619
660- return resolvedPath + redirectTo . slice ( path . length ) ;
620+ return redirectTo [ 0 ] === '/'
621+ ? resolvedPath + redirectTo . slice ( path . length )
622+ : joinUrlParts ( parentRoute . replace ( URL_PARAMETER_GLOBAL_REGEXP , '*' ) , resolvedPath ) ;
661623}
662624
663625/**
0 commit comments