Prerendering of concrete server route doesn't work, when using wildcard client route

未關閉
#32,490 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
30/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
停滯
技術堆疊
angular, typescript

研究方向

從連結的最小重現開始,執行其中的 ng build 以重現伺服器路由驗證錯誤。閱讀 app.server.config.ts 中的 serverRoutes 設定和 issue 的 routesFile 上下文,然後追蹤比較伺服器路由與用戶端萬用字元路由的 builder 行為。完成的標準是具體路由可以在不出現 mismatch 錯誤的情況下進行預先渲染,同時萬用字元路由仍由伺服器渲染。

由索引模型根據 Issue 內容生成。

描述

area: @angular/ssr freq1: low severity3: broken type: bug/fix
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:

  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/<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 分鐘
30 天內合併 PR
176

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

angular/angular-cli 的其他 Issue

查看 angular/angular-cli 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。