Prerendering of concrete server route doesn't work, when using wildcard client route
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 30/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 停滞
- 技術スタック
- angular, typescript
- 領域
- build-system, web-dev
調査の方向性
リンクされた最小再現から始め、その ng build を実行してサーバールートの検証エラーを再現します。app.server.config.ts の serverRoutes セットアップと、issue の routesFile コンテキストを読み、その後、サーバールートとクライアントのワイルドカードルートを比較する builder の挙動を追跡します。完了条件は、ワイルドカードルートが引き続きサーバーでレンダリングされる一方で、具体的なルートを mismatch エラーなしで事前レンダリングできることです。
索引モデルが issue の本文から書いたものです。
説明
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):
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:
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
`✘ [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
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:
- All those paths need to be formulated as client
Routesobjects (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. - We've been using for long time a custom
UrlSerializerto persist "site context params" in the URL, behind the "eyes" of the Routes, e.g.site.com/<brand>/<currency>/<langauge>/<........ 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:
[
{ path: 'product/:productCode', component: CmsDrivenComponent },
/* ... */
{ path: '**', component: CmsDrivenComponent },
]
please note that the prefix /<brand>/<langauge>/<currency>/ 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)
/**
* 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;
}
}
- 主要言語
- TypeScript
- スター
- 27k
- フォーク
- 11.8k
- 平均マージ
- 16時間 35分
- マージ済み PR(30日)
- 176
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
angular/angular-cli のほかの issue
-
area: @angular/build gemini-triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
angular/angular-cli#33955 ·
-
area: @angular/cli gemini-triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
angular/angular-cli#33055 · コメント 1 件 · リアクション 3 件 ·
-
area: @angular/build gemini-triaged
難易度 4/5 3〜5日 初心者へのやさしさ 72/100
angular/angular-cli#34137 ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34131 · 担当者 1 名 ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34130 · 担当者 1 名 ·
angular/angular-cli の issue をすべて見る
似ている issue
-
Browser Waiting for: Product Owner
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
getsentry/sentry-javascript#24577 · コメント 1 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
agilepathway/label-checker#640 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
copse-dev/agent-pane#2953 ·
-
agentic-workflows
難易度 1/5 1時間未満 初心者へのやさしさ 85/100
githubnext/rig#534 ·
-
automation missing-model model-sync provider:pioneer
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
anomalyco/models.dev#7701 ·