Prerendering of concrete server route doesn't work, when using wildcard client route
- Lingua principale
- TypeScript
- Stelle
- 27k
- Fork
- 11.8k
- Merge medio
- 14h 23m
- PR unite (30g)
- 162
Descrizione
### 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;
}
}
```
Guida per i contributori
Apri la guida per i contributori
Direzione di ricerca
Inizia dalla riproduzione minima collegata ed esegui il relativo `ng build` per riprodurre l’errore di convalida delle route del server. Leggi la configurazione di `serverRoutes` in `app.server.config.ts` e il contesto `routesFile` dell’issue, quindi segui il comportamento del builder che confronta le route del server con la route wildcard del client. Il lavoro è completato quando le route concrete possono essere prerenderizzate senza l’errore di mismatch, mentre la route wildcard continua a essere renderizzata dal server.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- angular, typescript
- Ambito
- build-system, web-dev
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Ferma
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 30/100