Skip to content

Conversation

alan-agius4
Copy link
Collaborator

@alan-agius4 alan-agius4 commented Apr 9, 2025

Handle getPrerenderParams return values when used with wildcard route paths, including support for combined routes like /product/:id/**.
Supports returning an array of path segments (e.g., ['category', '123']) for ** routes and dynamic segments combined with catch-all routes.

This enables more flexible prerendering configurations in server routes, including handling specific paths such as /product/1/laptop/123.

Example:

{
  path: '/product/:id/**',
  renderMode: RenderMode.Prerender,
  async getPrerenderParams() {
    return [
      { id: '1', '**': 'laptop/123' },
      { id: '2', '**': 'laptop/456' }
    ];
  }
}

Closes #30035

@alan-agius4 alan-agius4 added action: review The PR is still awaiting reviews from at least one requested reviewer target: major This PR is targeted for the next major release labels Apr 9, 2025
@alan-agius4 alan-agius4 requested a review from dgp1130 April 9, 2025 09:10
@alan-agius4 alan-agius4 added target: patch This PR is targeted for the next patch release and removed target: major This PR is targeted for the next major release labels Apr 9, 2025
@alan-agius4 alan-agius4 force-pushed the ssr-wildcard branch 4 times, most recently from e583b67 to ff6d1b4 Compare April 9, 2025 10:04
Copy link
Collaborator

@dgp1130 dgp1130 left a comment

Choose a reason for hiding this comment

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

What about a path like '/product/:first/**/:second'? Not sure if we allow segments after ** (reading this PR, seems like no?), but presumably we at least allow them before? Is this feature limited to either /:first xor /**?

I feel like we could support both with something like:

{
  path: '/product/:first/**/:second',
  renderMode: RenderMode.Prerender,
  async getPrerenderParams() {
    return [
      {'first': 'foo', __spread__: ['category', '1'], 'second': 'bar'},
      {'first': 'bar', __spread__: ['category', '2'], 'second': 'foo'},
    ];
  }
}

Not sure exactly the right way of identifying the spread array though. Maybe a Symbol imported from @angular/ssr?

Is there a better way of mixing ** and :foo syntax in the same path? Is :foo/** a rare enough pattern that we just don't care at this stage?

@alan-agius4
Copy link
Collaborator Author

alan-agius4 commented Apr 10, 2025

What about a path like '/product/:first/**/:second'? Not sure if we allow segments after ** (reading this PR, seems like no?), but presumably we at least allow them before? Is this feature limited to either /:first xor /**?

I feel like we could support both with something like:

{
  path: '/product/:first/**/:second',
  renderMode: RenderMode.Prerender,
  async getPrerenderParams() {
    return [
      {'first': 'foo', __spread__: ['category', '1'], 'second': 'bar'},
      {'first': 'bar', __spread__: ['category', '2'], 'second': 'foo'},
    ];
  }
}

Not sure exactly the right way of identifying the spread array though. Maybe a Symbol imported from @angular/ssr?

Is there a better way of mixing ** and :foo syntax in the same path? Is :foo/** a rare enough pattern that we just don't care at this stage?

/product/:first/**/:second is not a valid Angular router route. Also, we did not allow this before.

Edit
Thinking a bit more, /product/:first/** would be a valid route, but in that case, the route can also be defined as as /product/**.

@dgp1130
Copy link
Collaborator

dgp1130 commented Apr 11, 2025

Thinking a bit more, /product/:first/** would be a valid route, but in that case, the route can also be defined as as /product/**.

What about /product/:first/owner/**? I don't think we can generally assume you can always collapse the :params into **?

@alan-agius4
Copy link
Collaborator Author

alan-agius4 commented Apr 11, 2025

Thinking a bit more, /product/:first/** would be a valid route, but in that case, the route can also be defined as as /product/**.

What about /product/:first/owner/**? I don't think we can generally assume you can always collapse the :params into **?

Yes, this is not handled at the moment, maybe the simplest would be to allow something like instead of changing the signature of the function

{
  path: '/product/:first/**',
  renderMode: RenderMode.Prerender,
  async getPrerenderParams() {
    return [
      {'first': 'foo', '**': 'category/1', 'second': 'bar'},
    ];
  }
}

@dgp1130
Copy link
Collaborator

dgp1130 commented Apr 11, 2025

Would it be worth getting some wider feedback from the team (Andrew or Jan in particular)? Maybe a quick one-pager would be enough to get some quick thoughts on the approach.

I'm open to {'**': 'foo/bar'}, but also can see that being a little awkward. Presumably /foo/:**/bar is not allowed as a path so there's no ambiguity (if not, it probably should be banned)?

@jkrems
Copy link
Contributor

jkrems commented Apr 14, 2025

I like ** as a key because it's easy to remember. More generally, I'm in favor of an API that is consistently either a Record or an array, especially because of the mix case. Replacing /prod/:id/** with /prod/** removes the route param as a readable property on the activated route (or from component inputs if enabled), so it's not just an "free" change.

Afaict there's no pre-existing name for the value captured from the wildcard.

@alan-agius4
Copy link
Collaborator Author

@atscott, any thoughts?

@dgp1130
Copy link
Collaborator

dgp1130 commented Apr 14, 2025

@AndrewKushnir might also have some quick thoughts here.

@AndrewKushnir
Copy link

@AndrewKushnir might also have some quick thoughts here.

Thinking a bit more, /product/:first/** would be a valid route, but in that case, the route can also be defined as as /product/**.

I'm wondering what are the use-cases for this feature? What would be "hiding" behind the ** symbol: would we only have static paths or dynamic segments (like :first). My thinking is that allowing dynamic segments might be confusing, since in the getPrerenderParams() function we'd need to return a set of values for keys that are not present in the URL, so as a developer I'd need to reconcile this with corresponding routes in the main routing file (also it'd be a challenge to keep things in sync).

Potentially, we can apply the following rules:

  • ** can only be present at the end of the URL pattern
  • ** can only be capturing static segments (i.e. a pattern like products/** is not valid for products/:id), so there is always a direct mapping between dynamic segments and corresponding values returned from the getPrerenderParams() function

However, I'm not sure if that would be too limiting for the use-cases that this feature is improving.

@alan-agius4
Copy link
Collaborator Author

I'm wondering what are the use-cases for this feature?

One example is when catch-all routes need to be prerendered. For instance, see this issue: #30035. In such cases, parameterized routes can't be used effectively because the nesting depth is arbitrary.

As for the rules you mentioned, those sound good to me, and I don't see them as restrictive.

@alan-agius4
Copy link
Collaborator Author

@dgp1130 PTAL

});
const routeWithResolvedParams = currentRoutePath
.replace(URL_PARAMETER_REGEXP, handlePrerenderParamsReplacement(params, currentRoutePath))
.replace(CATCH_ALL_REGEXP, handlePrerenderParamsReplacement(params, currentRoutePath));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question: What happens if the user does foo/**/bar? Do they at least get an error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You already get an error for that as it's not a valid angular router route.

Comment on lines 170 to 171
* { id: '1', '**': '/laptop/123' },
* { id: '2', '**': '/laptop/456' }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Is the leading slash necessary? If not, I'd probably leave it off given that this is replacing a ** which is already preceded by a slash.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not it is not

@alan-agius4 alan-agius4 added action: merge The PR is ready for merge by the caretaker and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Apr 23, 2025
Handle `getPrerenderParams` return values when used with wildcard route paths, including support for combined routes like `/product/:id/**`.
Supports returning an array of path segments (e.g., `['category', '123']`) for `**` routes and dynamic segments combined with catch-all routes.

This enables more flexible prerendering configurations in server routes, including handling specific paths such as `/product/1/laptop/123`.

Example:
```ts
{
  path: '/product/:id/**',
  renderMode: RenderMode.Prerender,
  async getPrerenderParams() {
    return [
      { id: '1', '**': 'laptop/123' },
      { id: '2', '**': 'laptop/456' }
    ];
  }
}
```

Closes angular#30035
@alan-agius4 alan-agius4 merged commit 8bfcae4 into angular:main Apr 23, 2025
53 of 54 checks passed
@alan-agius4 alan-agius4 deleted the ssr-wildcard branch April 23, 2025 07:43
@alan-agius4 alan-agius4 added target: minor This PR is targeted for the next minor release and removed target: patch This PR is targeted for the next patch release target: minor This PR is targeted for the next minor release labels Apr 23, 2025
@alan-agius4 alan-agius4 added the target: major This PR is targeted for the next major release label Apr 23, 2025
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators May 24, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

action: merge The PR is ready for merge by the caretaker area: @angular/ssr target: major This PR is targeted for the next major release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot pre-render wildcard routes when using outputMode: 'server'

4 participants