Skip to content

fix(@angular/ssr): substitute route parameters used in redirectTo - #34411

Closed
MeAkib wants to merge 1 commit into
angular:mainfrom
MeAkib:fix/ssr-redirect-route-params
Closed

MeAkib wants to merge 1 commit into
angular:mainfrom
MeAkib:fix/ssr-redirect-route-params

Conversation

@MeAkib

@MeAkib MeAkib commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

When a route redirected to a path using one of its parameters (e.g. old/:id to new/:id), the server responded with the literal placeholder, Location: /new/:id, while the Angular router navigates to /new/5. Substitute the matched values, and for prerendered routes the getPrerenderParams values.

Fixes #34410

When a route redirected to a path using one of its parameters (e.g. `old/:id` to `new/:id`), the server
responded with the literal placeholder, `Location: /new/:id`, while the Angular router navigates to
`/new/5`. Substitute the matched values, and for prerendered routes the `getPrerenderParams` values.

Fixes angular#34410

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the Angular SSR router's redirect handling by supporting route parameter substitution (e.g., ':id') in redirect paths, aligning with the Angular router's behavior. It introduces '*' placeholders to handle parameters located at different segment positions and updates 'buildPathWithParams' to resolve them. The feedback highlights two critical issues where query parameters or fragments in redirect paths can interfere with segment splitting and prevent proper parameter substitution. Actionable code suggestions are provided to extract and re-append query parameters and fragments during path resolution.

Comment on lines +608 to +622
return target
.split('/')
.map((segment, index) => {
if (segment[0] !== ':') {
return segment;
}

const sourceIndex = sourceSegments.indexOf(segment);
if (sourceIndex === -1) {
return segment;
}

return joinUrlParts(parentRoute.replace(URL_PARAMETER_GLOBAL_REGEXP, '*'), path);
return sourceIndex === index ? '*' : `*${sourceIndex}`;
})
.join('/');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When redirectTo is an absolute path containing query parameters or a fragment (e.g., /new/:id?param=value), splitting the target directly by / leaves the query/fragment attached to the last segment (e.g., :id?param=value). This prevents the parameter from being matched against sourceSegments and substituted, resulting in an unresolved redirect. Splitting the query/fragment before processing the path segments and re-appending it afterwards resolves this issue.

  const queryOrFragmentIndex = target.search(/[?#]/);
  const path = queryOrFragmentIndex === -1 ? target : target.slice(0, queryOrFragmentIndex);
  const queryAndFragment = queryOrFragmentIndex === -1 ? '' : target.slice(queryOrFragmentIndex);

  const resolvedPath = path
    .split('/')
    .map((segment, index) => {
      if (segment[0] !== ':') {
        return segment;
      }

      const sourceIndex = sourceSegments.indexOf(segment);
      if (sourceIndex === -1) {
        return segment;
      }

      return sourceIndex === index ? '*' : '*' + sourceIndex;
    })
    .join('/');

  return resolvedPath + queryAndFragment;

Comment on lines 198 to 210
const fromPathParts = fromPath.split('/');
const toPathParts = toPath.split('/');
const resolvedParts = toPathParts.map((part, index) =>
toPathParts[index] === '*' ? fromPathParts[index] : part,
);
const resolvedParts = toPathParts.map((part, index) => {
// `*` takes the segment at the same position, `*<n>` the segment at position `n`.
const match = /^\*(\d*)$/.exec(part);
if (!match) {
return part;
}

return fromPathParts[match[1] ? Number(match[1]) : index];
});

return joinUrlParts(...resolvedParts);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If toPath contains query parameters or a fragment (e.g., /new/*?param=value), splitting the path directly by / leaves the query/fragment attached to the last segment (e.g., *?param=value). This prevents the regex /^\*(\d*)$/ from matching, so the placeholder is not replaced. Splitting the query/fragment before processing the path segments and re-appending it afterwards resolves this issue.

  const queryOrFragmentIndex = toPath.search(/[?#]/);
  const path = queryOrFragmentIndex === -1 ? toPath : toPath.slice(0, queryOrFragmentIndex);
  const queryAndFragment = queryOrFragmentIndex === -1 ? '' : toPath.slice(queryOrFragmentIndex);

  const fromPathParts = fromPath.split('/');
  const toPathParts = path.split('/');
  const resolvedParts = toPathParts.map((part, index) => {
    // `*` takes the segment at the same position, `*<n>` the segment at position `n`.
    const match = /^\\*(\\d*)$/.exec(part);
    if (!match) {
      return part;
    }

    return fromPathParts[match[1] ? Number(match[1]) : index];
  });

  return joinUrlParts(...resolvedParts) + queryAndFragment;

@alan-agius4

Copy link
Copy Markdown
Collaborator

Thanks, but this is already being handled in #34321

@MeAkib
MeAkib deleted the fix/ssr-redirect-route-params branch October 20, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSR redirects a redirectTo that uses a route parameter to the literal :id instead of the parameter value

2 participants