Skip to content

@angular/build: lazy routes get no SSR modulepreload hints when the app's tsconfig is not at the workspace root (ɵentryName relative to basePath, chunk map relative to workspace root) #34369

Description

@IsraChido

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

When an application's tsconfig.app.json is not at the workspace root, SSR adds no <link rel="modulepreload"> for any lazy route. That includes the CLI's own multi-project layout (ng generate application puts it in projects/<name>/) and every Nx workspace. The routes are written in the shape lazyRoutesTransformer recognises, and the pages render correctly. Only the hints are missing, and nothing warns about it.

Cause. The two sides of the lookup use different base directories:

  • lazyRoutesTransformer stamps ɵentryName as a path relative to compilerOptions.basePath (lazy-routes-transformer.ts#L151). The compiler sets basePath to the directory of the tsconfig, here projects/site.
  • generateLazyLoadedFilesMappings keys entryPointToBrowserMapping by esbuild's entryPoint (manifest.ts#L262). That path is relative to absWorkingDir, which is the workspace root (application-code-bundle.ts#L631).

So the stamp reads src/app/home/home.ts and the key reads projects/site/src/app/home/home.ts. appendPreloadToMetadata finds nothing and every route's preload stays empty. When the tsconfig is at the workspace root, as in a default ng new, the two bases happen to coincide, which is probably why this has gone unnoticed.

projects/site/tsconfig.app.json ɵentryName in the server bundle preload in angular-app-manifest.mjs route hints in index.html / pricing/index.html
as generated src/app/home/home.ts none 0 / 0
"angularCompilerOptions": { "basePath": "." } added projects/site/src/app/home/home.ts on both routes 1 / 1 (the route's own chunk)

Workaround. "angularCompilerOptions": { "basePath": "." } in the app's tsconfig. It works only because the value is read raw and resolved against the process cwd, and the build happens to run from the workspace root.

Expected. Route hints do not depend on where the tsconfig lives. One option is for the transformer to stamp paths relative to the workspace root, which the builder already knows, instead of relative to basePath.

Real-world impact. In our Nx workspace (22 lazy routes, @nx/angular:application, which delegates to buildApplication unchanged), no route had ever received a hint. With the workaround every route does. On the home page, the shell route's chunk finishes 5.8 s earlier in throttled Lighthouse runs (mobile, devtools throttling, 8× CPU, 5 runs), and TTI drops by 1.3 s.

Minimal Reproduction

npx -p @angular/cli@22.2.3 ng new ws --create-application=false --skip-git --package-manager=npm --defaults --ai-config=none
cd ws
npx ng generate application site --ssr --routing --style=css --skip-tests --defaults

projects/site/src/app/app.routes.ts

import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', loadComponent: () => import('./home/home').then((m) => m.Home) },
  {
    path: 'pricing',
    loadComponent: () => import('./pricing/pricing').then((m) => m.Pricing),
  },
];

projects/site/src/app/home/home.ts (and pricing/pricing.ts, the same with Pricing)

import { Component } from '@angular/core';

@Component({ selector: 'app-home', template: '<h1>Home</h1>' })
export class Home {}
npx ng build site
grep -c '"preload"' dist/site/server/angular-app-manifest.mjs                    # 0
grep -o '<link rel="modulepreload"[^>]*>' dist/site/browser/index.html           # only the shared initial chunk
grep -rhoE 'entryName:"[^"]*"' dist/site/server/*.mjs                           # src/app/home/home.ts, src/app/pricing/pricing.ts

Then add "angularCompilerOptions": { "basePath": "." } to projects/site/tsconfig.app.json and build again. Both routes get preload, each page gets its own chunk's hint, and the stamps become projects/site/src/app/….

Exception or Error

None. The build succeeds and the pages render; only the lazy-route modulepreload hints are missing.

Your Environment

Angular CLI       : 22.2.3
Angular           : 22.2.3
Node.js           : 24.20.0
Package Manager   : npm 11.19.0
Operating System  : darwin arm64

@angular/build            22.2.3
@angular/cli              22.2.3
@angular/common           22.2.3
@angular/compiler         22.2.3
@angular/compiler-cli     22.2.3
@angular/core             22.2.3
@angular/platform-browser 22.2.3
@angular/platform-server  22.2.3
@angular/router           22.2.3
@angular/ssr              22.2.3
typescript                6.0.3

Anything else relevant?

Also observed on 22.2.1, in our Nx workspace. The transformer's relative(basePath, …) is unchanged since it was introduced in 8d7a51d ("feat(@angular/ssr): add modulepreload for lazy-loaded routes").

Activity

  1. added this to the needsTriage milestone on Oct 16, 2026
  2. self-assigned this
    on Oct 16, 2026
  3. added 2 commits that reference this issue on Oct 16, 2026
    8a02c48
    997bf67
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions