Skip to content

SSG: when hydrating Angular renders contradicting state in @if-else, @switch, [ngSwitch], *ngTemplateOutlet #60218

@KonstantinasL

Description

@KonstantinasL

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

Yes

Description

When hydrating a prerendered page (SSG) and there are differences in states, Angular displays both the content that was pre-rendered and the live content at the same time, resulting in duplicated content which impacts very negatively UX and CWV metrics (like CLS).

@Component({
  selector: 'app-demo',
  template: `
  <div> <!-- for a short while both A and B will be visible, when app becomes stable A will disappear -->
    @if (variant === 'A') {
      <span>A</span>
    } @else {
      <span>B</span>
    }
  </div>

  <div> <!-- for a short while both A and B will be visible, when app becomes stable A will disappear -->
    @switch (variant) {
      @case('A') { <span>A</span> }
      @case('B') { <span>B</span> }
    }
  </div>

  <div><!-- for a short while both A and B will be visible, when app becomes stable A will disappear -->
    <ng-container [ngSwitch]="variant">
      <span *ngSwithCase="A">A</span>
      <span *ngSwithCase="B">B</span>
    </ng-container>
  </div>

  <div><!-- for a short while both A and B will be visible, when app becomes stable A will disappear -->
    <ng-container *ngTemplateOutlet="variant === 'A' ? templateA : templateB" />
    <ng-template #templateA>
       <span>A</span>
    </ng-template>
    <ng-template #templateB>
       <span>B</span>
    </ng-template>
  </div>

  <div><!-- this will work fine, only A or B will be visible at any time -->
    <ng-container *ngIf="variant === 'A'; else templateB">
       <span>A</span>
    </ng-container>
    <ng-template #templateB>
       <span>B</span>
    </ng-template>
  </div>
  `,
  imports: [CommonModule],
})
export class DemoComponent implements OnInit {
  private readonly platformId = inject(PLATFORM_ID);
  variant = 'A';
  
  ngOnInit(): void {
    // simulating different state, in the browser than it was in the server
    // IRL, this could be any state change: user permissions, authentication, query parameters, etc.
    if (isPlatformBrowser(this.platformId)) {
       this.variant = 'B';
    }
  }
}

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

No error

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 19.2.0
Node: 22.7.0
Package Manager: npm 10.8.2
OS: win32 x64

Angular: 19.2.0
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, localize, platform-browser
... platform-browser-dynamic, platform-server, router, ssr

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1902.0
@angular-devkit/build-angular   19.2.0
@angular-devkit/core            19.2.0
@angular-devkit/schematics      19.2.0
@angular/cdk                    19.2.1
@schematics/angular             19.0.6
ng-packagr                      19.0.1
rxjs                            7.8.2
typescript                      5.6.3
zone.js                         0.15.0

Anything else?

This is a duplicate for a 58670 issue (which is now closed but the bug is still present), the 58670 has some live examples.

This issue seems to be related to "withI18nSupport" hydration option even though none of the elements has i18n properties on them.

Metadata

Metadata

Labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions