Prerendering of concrete server route doesn't work, when using wildcard client route
- Ngôn ngữ chính
- TypeScript
- Star
- 27k
- Fork
- 11.8k
- Merge trung bình
- 14 giờ 23 phút
- Pull request đã merge (30 ngày)
- 162
Mô tả
### Command
build
### Is this a regression?
- [ ] Yes, this behavior used to work in the previous version
### The previous version in which this bug was not present was
_No response_
### Description
With `outputMode: server`,
when client Routes contain just a wildcard route (e.g. because rendering is CMS-driven and looks up the current URL dynamically to fetch the page structure):
```ts
export const routes: Routes = [
{
path: '**',
component: CmsDrivenComponent,
},
];
```
and when we want to prerender only some concrete routes, defined as `ServerRoute` objects in the `app.server.config.ts`:
```ts
export const serverRoutes: ServerRoute[] = [
{
path: 'some-concrete-page',
renderMode: RenderMode.Prerender,
},
{
path: '**',
renderMode: RenderMode.Server,
},
];
```
Unfortunately we get the error during the `ng build`:
`✘ [ERROR] The 'some-concrete-page' server route does not match any routes defined in the Angular routing configuration (typically provided as a part of the 'provideRouter' call). Please make sure that the mentioned server route is present in the Angular routing configuration.`
Note: Before `outputMode: server` was introduced, it was possible for feed Angular builder with a `routesFile` TXT list of concrete URL paths to prerender (and `discoverRoutes: false`).
IMHO it should be still possible to feed Angular builder with a `routesFile` TXT list of concrete URL paths to prerender, even with `outputMode: server` - to benefit from the new `AngularNodeAppEngine` instead of deprecated `CommonEngine`.
### Minimal Reproduction
Minimal reproduction repo. For more, see its readme file:
https://github.com/Platonn/test-ng21-prerender-concrete-server-route-doesnt-work-with-client-wildcard-route
### Exception or Error
```text
`✘ [ERROR] The 'some-concrete-page' server route does not match any routes defined in the Angular routing configuration (typically provided as a part of the 'provideRouter' call). Please make sure that the mentioned server route is present in the Angular routing configuration.`
```
### Your Environment
```text
ng version
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI : 21.1.2
Angular : 21.1.2
Node.js : 24.8.0
Package Manager : npm 11.6.0
Operating System : darwin arm64
┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build │ 21.1.2 │ ^21.1.1 │
│ @angular/cli │ 21.1.2 │ ^21.1.1 │
│ @angular/common │ 21.1.2 │ ^21.1.0 │
│ @angular/compiler │ 21.1.2 │ ^21.1.0 │
│ @angular/compiler-cli │ 21.1.2 │ ^21.1.0 │
│ @angular/core │ 21.1.2 │ ^21.1.0 │
│ @angular/forms │ 21.1.2 │ ^21.1.0 │
│ @angular/platform-browser │ 21.1.2 │ ^21.1.0 │
│ @angular/platform-server │ 21.1.2 │ ^21.1.0 │
│ @angular/router │ 21.1.2 │ ^21.1.0 │
│ @angular/ssr │ 21.1.2 │ ^21.1.1 │
│ rxjs │ 7.8.2 │ ~7.8.0 │
│ typescript │ 5.9.3 │ ~5.9.2 │
│ vitest │ 4.0.18 │ ^4.0.8 │
│ zone.js │ 0.16.0 │ ~0.16.0 │
```
### Anything else relevant?
**It would be great to allow for an option to prerender an arbitrary list of routes passed via `routesFile` TXT back again**. The current requirement to expose all paths for prerendering in the `app.config.server.ts` has the following limitations:
1. All those paths need to be formulated as client `Routes` objects (with component,resolvers,guards) to avoid error in build time ("`server route does not match any routes defined in the Angular routing configuration`"), while we'd like to have just one generic `**` cms-driven route defined once.
2. We've been using for long time a custom `UrlSerializer` to persist "site context params" in the URL, behind the "eyes" of the Routes, e.g. `site.com////<........ regular angular route can see only here ........>` and 2-way synchronize those "site context params" persisted in the url with the application's state.
So I'd like to prerender the following list of paths:
```
/electronics-spa/en/USD/product/111
/electronics-spa/en/USD/product/222
/electronics-spa/de/USD/product/111
/electronics-spa/de/USD/product/222
/electronics-spa/de/EUR/product/111
/electronics-spa/de/EUR/product/222
/apparel-uk-spa/en/USD/product/888
/apparel-uk-spa/en/USD/product/999
/apparel-uk-spa/de/USD/product/888
/apparel-uk-spa/de/USD/product/999
/apparel-uk-spa/de/EUR/product/888
/apparel-uk-spa/de/EUR/product/999
```
but the actual client Routes are just:
```ts
[
{ path: 'product/:productCode', component: CmsDrivenComponent },
/* ... */
{ path: '**', component: CmsDrivenComponent },
]
```
please note that the prefix `////` is invisible to the Angular Router thanks to the custom `UrlSerializer` which intercepts those params and synchronizes with the app's state of active brand, language and currency. Let me paste below just a brief implementation idea of our custom `UrlSerializer` (but for full implementation in Spartacus, our open-source Angular meta-framework for building ecommerce sites, see [here](https://github.com/SAP/spartacus/blob/3f6dba920c9d167c9c957f0a530b665f556fe1f1/projects/core/src/site-context/services/site-context-url-serializer.ts))
```ts
/**
* Values of the site context parameters encoded in the URL.
*/
export interface SiteContextUrlParams {
brand?: string,
language?: string,
currency?: string
}
/**
* UrlTree decorated with a custom property `siteContext`
* for storing the values of the site context parameters.
*/
export interface UrlTreeWithSiteContext extends UrlTree {
siteContext?: SiteContextUrlParams;
}
/**
* Angular URL Serializer aware of Spartacus site context parameters encoded in the URL.
*/
@Injectable()
export class SiteContextUrlSerializer extends DefaultUrlSerializer {
/**
* @override Recognizes the site context parameters encoded in the prefix segments
* of the given URL.
*
* It returns the UrlTree for the given URL shortened by the recognized params, but saves
* the params' values in the custom property of UrlTree: `siteContext`.
*/
parse(url: string): UrlTreeWithSiteContext {
const urlWithParams = this.urlExtractContextParameters(url);
const parsed = super.parse(urlWithParams.url) as UrlTreeWithSiteContext;
this.urlTreeIncludeContextParameters(parsed, urlWithParams.params);
return parsed;
}
/**
* Recognizes the site context parameters encoded in the prefix segments of the given URL.
*
* It returns the recognized site context params as well as the
* URL shortened by the recognized params.
* @example For `/electronics-spa/USD/en/checkout` it returns
* `{ url: `/checkout`, params: { brand: 'electronics-spa', currency: 'USD', langauge: 'en' } }`
* ```
*/
urlExtractContextParameters(url: string): {
url: string;
params: SiteContextUrlParams;
} {
/*...*/
}
/**
* Saves the given site context parameters in the custom property
* of the given UrlTree: `siteContext`.
*/
protected urlTreeIncludeContextParameters(
urlTree: UrlTreeWithSiteContext,
params: SiteContextUrlParams
): void {
urlTree.siteContext = params;
}
/**
* @override Serializes the given UrlTree to a string and prepends
* to it the current values of the site context parameters.
*/
serialize(tree: UrlTreeWithSiteContext): string {
const params = this.urlTreeExtractContextParameters(tree);
const url = super.serialize(tree);
const serialized = this.urlIncludeContextParameters(url, params);
return serialized;
}
/**
* Returns the site context parameters stored in the custom property
* of the UrlTree: `siteContext`.
*/
urlTreeExtractContextParameters(
urlTree: UrlTreeWithSiteContext
): SiteContextUrlParams {
return urlTree.siteContext ?? {};
}
/**
* Prepends the current values of the site context parameters to the given URL.
* @example
* For `{ url: `/checkout`, params: { brand: 'electronics-spa', currency: 'USD', langauge: 'en' } }`
* it returns `/electronics-spa/USD/en/checkout`
*/
protected urlIncludeContextParameters(
url: string,
params: SiteContextUrlParams
): string {
const contextRoutePart = `${brand}/${currency}/${langauge}`
return contextRoutePart + url;
}
}
```
Hướng dẫn đóng góp
Hướng nghiên cứu
Bắt đầu với bản tái hiện tối thiểu được liên kết và chạy `ng build` của bản đó để tái hiện lỗi xác thực route của server. Đọc thiết lập `serverRoutes` trong `app.server.config.ts` và ngữ cảnh `routesFile` của issue, sau đó lần theo hành vi của builder khi so sánh các route của server với route wildcard của client. Được coi là hoàn tất khi các route cụ thể có thể được prerender mà không có lỗi mismatch, trong khi route wildcard vẫn được render phía server.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- angular, typescript
- Lĩnh vực
- build-system, web-dev
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 30/100