8000 SSR outputs <!--container--> instead of HTML as per template syntax · Issue #60871 · angular/angular · GitHub
[go: up one dir, main page]

Skip to content

SSR outputs <!--container--> instead of HTML as per template syntax #60871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
davidbusuttil opened this issue Apr 15, 2025 · 13 comments
Closed
Labels
area: common Issues related to APIs in the @angular/common package area: server Issues related to server-side rendering common: image directive needs reproduction This issue needs a reproduction in order for the team to investigate further
Milestone

Comments

@davidbusuttil
Copy link
davidbusuttil commented Apr 15, 2025

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

Don't known / other

Is this a regression?

No

Description

Introduction

I am using Firestore to store data for a CMS.

The data is stored in a field called blocks.

The CMS is divided into two components: Block and Inline.

Block Component

block.component.ts

import { ChangeDetectionStrategy, Component, input, ViewEncapsulation } from "@angular/core";
import { NgClass } from "@angular/common";

import { InlineComponent } from "../inline/inline.component";

import { JoinArrayPipe } from "../../pipes/join-array/join-array.pipe";
import { BlockFlowPipe } from "../../pipes/block-flow/block-flow.pipe";
import { BlockFlowRootPipe } from "../../pipes/block-flow-root/block-flow-root.pipe";
import { SectionPipe } from "../../pipes/section/section.pipe";
import { ArticlePipe } from "../../pipes/article/article.pipe";
import { AsidePipe } from "../../pipes/aside/aside.pipe";
import { MeshPipe } from "../../pipes/mesh/mesh.pipe";
import { HeadingPipe } from "../../pipes/heading/heading.pipe";
import { ParagraphPipe } from "../../pipes/paragraph/paragraph.pipe";
import { AddressPipe } from "../../pipes/address/address.pipe";
import { ListPipe } from "../../pipes/list/list.pipe";
import { MenuPipe } from "../../pipes/menu/menu.pipe";
import { DescriptionPipe } from "../../pipes/description/description.pipe";
import { SeparatorPipe } from "../../pipes/separator/separator.pipe";

import { Block } from "../../types/block/block";

@Component({
  selector: "[contentBlock]",
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  imports: [
    NgClass,
    InlineComponent,
    JoinArrayPipe,
    BlockFlowPipe,
    BlockFlowRootPipe,
    SectionPipe,
    ArticlePipe,
    AsidePipe,
    MeshPipe,
    HeadingPipe,
    ParagraphPipe,
    AddressPipe,
    ListPipe,
    MenuPipe,
    DescriptionPipe,
    SeparatorPipe,
  ],
  templateUrl: "./block.component.html",
})
export class BlockComponent {
  readonly blocks = input.required<Block[]>({ alias: "contentBlock" });
}

block.component.html

@for (block of blocks(); track $index) {
  @switch (block["@type"]) {
    @case ("block-flow") {
      @let blockFlow = block | blockFlow;
      <div
        class="block-flow"
        [ngClass]="blockFlow.ngClass"
        [attr.id]="blockFlow.id"
        [attr.lang]="blockFlow.lang"
        [attr.title]="blockFlow.title"
        [contentBlock]="blockFlow.blocks"
      ></div>
    }
    @case ("block-flow-root") {
      @let blockFlowRoot = block | blockFlowRoot;
      <div
        class="block-flow"
        [ngClass]="blockFlowRoot.ngClass"
        [attr.id]="blockFlowRoot.id"
        [attr.lang]="blockFlowRoot.lang"
        [attr.title]="blockFlowRoot.title"
        [contentBlock]="blockFlowRoot.blocks"
      ></div>
    }
    @case ("section") {
      @let section = block | section;
      <section
        class="section"
        [ngClass]="section.ngClass"
        [attr.id]="section.id"
        [attr.lang]="section.lang"
        [attr.title]="section.title"
        [contentBlock]="section.blocks"
      ></section>
    }
    @case ("article") {
      @let article = block | article;
      <article
        class="article"
        [ngClass]="article.ngClass"
        [attr.id]="article.id"
        [attr.lang]="article.lang"
        [attr.title]="article.title"
        [contentBlock]="article.blocks"
      ></article>
    }
    @case ("aside") {
      @let aside = block | aside;
      <aside
        class="aside"
        [ngClass]="aside.ngClass"
        [attr.id]="aside.id"
        [attr.lang]="aside.lang"
        [attr.title]="aside.title"
        [contentBlock]="aside.blocks"
      ></aside>
    }
    @case ("mesh") {
      @let mesh = block | mesh;
      <div class="mesh" [ngClass]="mesh.ngClass" [attr.id]="mesh.id" [attr.lang]="mesh.lang" [attr.title]="mesh.title">
        @for (meshItem of mesh.meshItems; track $index) {
          <div
            class="mesh-item"
            [ngClass]="meshItem.ngClass"
            [attr.id]="meshItem.id"
            [attr.lang]="meshItem.lang"
            [attr.title]="meshItem.title"
            [contentBlock]="meshItem.blocks"
          ></div>
        }
      </div>
    }
    @case ("heading") {
      @let heading = block | heading;
      @switch (heading.level) {
        @case (1) {
          <h1
            class="heading"
            [ngClass]="['x-small:size:medium', 'small:size:large', 'medium:size:x-large'] | joinArray: heading.ngClass"
            [attr.id]="heading.id"
            [attr.lang]="heading.lang"
            [attr.title]="heading.title"
            [contentInline]="heading.inlines"
          ></h1>
        }
        @case (2) {
          <h2
            class="heading"
            [ngClass]="['x-small:size:small', 'small:size:medium', 'medium:size:large'] | joinArray: heading.ngClass"
            [attr.id]="heading.id"
            [attr.lang]="heading.lang"
            [attr.title]="heading.title"
            [contentInline]="heading.inlines"
          ></h2>
        }
        @case (3) {
          <h3
            class="heading"
            [ngClass]="['x-small:size:x-small', 'small:size:small', 'medium:size:medium'] | joinArray: heading.ngClass"
            [attr.id]="heading.id"
            [attr.lang]="heading.lang"
            [attr.title]="heading.title"
            [contentInline]="heading.inlines"
          ></h3>
        }
      }
    }
    @case ("paragraph") {
      @let paragraph = block | paragraph;
      <p
        class="paragraph"
        [ngClass]="['x-small:size:medium', 'small:size:large', 'medium:size:x-large'] | joinArray: paragraph.ngClass"
        [attr.id]="paragraph.id"
        [attr.lang]="paragraph.lang"
        [attr.title]="paragraph.title"
        [contentInline]="paragraph.inlines"
      ></p>
    }
    @case ("address") {
      @let address = block | address;
      <p
        class="address"
        [ngClass]="['x-small:size:medium', 'small:size:large', 'medium:size:x-large'] | joinArray: address.ngClass"
        [attr.id]="address.id"
        [attr.lang]="address.lang"
        [attr.title]="address.title"
        [contentInline]="address.inlines"
      ></p>
    }
    @case ("list") {
      @let list = block | list;
      @switch (list.type) {
        @case ("ordered") {
          <ol
            class="list"
            [ngClass]="
              ['x-small:size:medium', 'small:size:large', 'medium:size:x-large', 'x-small:type:ordered']
                | joinArray: list.ngClass
            "
            [attr.id]="list.id"
            [attr.lang]="list.lang"
            [attr.title]="list.title"
          >
            @for (listItem of list.listItems; track $index) {
              <li
                class="list-item"
                [ngClass]="listItem.ngClass"
                [attr.id]="listItem.id"
                [attr.lang]="listItem.lang"
                [attr.title]="listItem.title"
                [contentInline]="listItem.inlines"
              ></li>
            }
          </ol>
        }
        @case ("unordered") {
          <ul
            class="list"
            [ngClass]="
              ['x-small:size:medium', 'small:size:large', 'medium:size:x-large', 'x-small:type:unordered']
                | joinArray: list.ngClass
            "
            [attr.id]="list.id"
            [attr.lang]="list.lang"
            [attr.title]="list.title"
          >
            @for (listItem of list.listItems; track $index) {
              <li
                class="list-item"
                [ngClass]="listItem.ngClass"
                [attr.id]="listItem.id"
                [attr.lang]="listItem.lang"
                [attr.title]="listItem.title"
                [contentInline]="listItem.inlines"
              ></li>
            }
          </ul>
        }
      }
    }

8000
    @case ("menu") {
      @let menu = block | menu;
      <ul
        class="menu"
        [ngClass]="['x-small:size:medium', 'small:size:large', 'medium:size:x-large'] | joinArray: menu.ngClass"
        [attr.id]="menu.id"
        [attr.lang]="menu.lang"
        [attr.title]="menu.title"
      >
        @for (menuItem of menu.menuItems; track $index) {
          <li
            class="menu-item"
            [ngClass]="menuItem.ngClass"
            [attr.id]="menuItem.id"
            [attr.lang]="menuItem.lang"
            [attr.title]="menuItem.title"
            [contentInline]="menuItem.inlines"
          ></li>
        }
      </ul>
    }
    @case ("description") {
      @let description = block | description;
      <dl
        class="description"
        [ngClass]="['x-small:size:medium', 'small:size:large', 'medium:size:x-large'] | joinArray: description.ngClass"
        [attr.id]="description.id"
        [attr.lang]="description.lang"
        [attr.title]="description.title"
      >
        @for (descriptionItem of description.descriptionItems; track $index) {
          @switch (descriptionItem.type) {
            @case ("term") {
              <dt
                class="description-term"
                [ngClass]="descriptionItem.ngClass"
                [attr.id]="descriptionItem.id"
                [attr.lang]="descriptionItem.lang"
                [attr.title]="descriptionItem.title"
                [contentInline]="descriptionItem.inlines"
              ></dt>
            }
            @case ("detail") {
              <dd
                class="description-detail"
                [ngClass]="descriptionItem.ngClass"
                [attr.id]="descriptionItem.id"
                [attr.lang]="descriptionItem.lang"
                [attr.title]="descriptionItem.title"
                [contentInline]="descriptionItem.inlines"
              ></dd>
            }
          }
        }
      </dl>
    }
    @case ("separator") {
      @let separator = block | separator;
      <hr class="separator" [ngClass]="separator.ngClass" />
    }
  }
}
<ng-content />

Inline Component

inline.component.ts

import { ChangeDetectionStrategy, Component, input, ViewEncapsulation } from "@angular/core";
import { NgClass, NgOptimizedImage } from "@angular/common";
import { RouterLink } from "@angular/router";

import { JoinArrayPipe } from "../../pipes/join-array/join-array.pipe";
import { InlineFlowPipe } from "../../pipes/inline-flow/inline-flow.pipe";
import { InlineFlowRootPipe } from "../../pipes/inline-flow-root/inline-flow-root.pipe";
import { HiddenPipe } from "../../pipes/hidden/hidden.pipe";
import { StrongPipe } from "../../pipes/strong/strong.pipe";
import { EmphasisPipe } from "../../pipes/emphasis/emphasis.pipe";
import { AbbreviationPipe } from "../../pipes/abbreviation/abbreviation.pipe";
import { TimePipe } from "../../pipes/time/time.pipe";
import { SubscriptPipe } from "../../pipes/subscript/subscript.pipe";
import { SuperscriptPipe } from "../../pipes/superscript/superscript.pipe";
import { LinkPipe } from "../../pipes/link/link.pipe";
import { AnchorPipe } from "../../pipes/anchor/anchor.pipe";
import { ButtonPipe } from "../../pipes/button/button.pipe";
import { ImagePipe } from "../../pipes/image/image.pipe";
import { LogoPipe } from "../../pipes/logo/logo.pipe";
import { IconPipe } from "../../pipes/icon/icon.pipe";
import { LinebreakPipe } from "../../pipes/linebreak/linebreak.pipe";

import { Inline } from "../../types/inline/inline";

@Component({
  selector: "[contentInline]",
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  imports: [
    NgClass,
    NgOptimizedImage,
    RouterLink,
    JoinArrayPipe,
    InlineFlowPipe,
    InlineFlowRootPipe,
    HiddenPipe,
    StrongPipe,
    EmphasisPipe,
    AbbreviationPipe,
    TimePipe,
    SubscriptPipe,
    SuperscriptPipe,
    LinkPipe,
    AnchorPipe,
    ButtonPipe,
    ImagePipe,
    LogoPipe,
    IconPipe,
    LinebreakPipe,
  ],
  templateUrl: "./inline.component.html",
})
export class InlineComponent {
  readonly inlines = input.required<Inline[]>({ alias: "contentInline" });
}

inline.component.html

@for (inline of inlines(); track $index) {
  @switch (inline["@type"]) {
    @case ("inline-flow") {
      @let inlineFlow = inline | inlineFlow;
      <span
        class="inline-flow"
        [ngClass]="inlineFlow.ngClass"
        [attr.id]="inlineFlow.id"
        [attr.lang]="inlineFlow.lang"
        [attr.title]="inlineFlow.title"
        [contentInline]="inlineFlow.inlines ?? []"
        >{{ inlineFlow.text }}</span
      >
    }
    @case ("inline-flow-root") {
      @let inlineFlowRoot = inline | inlineFlowRoot;
      <span
        class="inline-flow-root"
        [ngClass]="inlineFlowRoot.ngClass"
        [attr.id]="inlineFlowRoot.id"
        [attr.lang]="inlineFlowRoot.lang"
        [attr.title]="inlineFlowRoot.title"
        [contentInline]="inlineFlowRoot.inlines ?? []"
        >{{ inlineFlowRoot.text }}</span
      >
    }
    @case ("hidden") {
      @let hidden = inline | hidden;
      <span
        class="hidden"
        [ngClass]="hidden.ngClass"
        [attr.id]="hidden.id"
        [attr.lang]="hidden.lang"
        [attr.title]="hidden.title"
        [contentInline]="hidden.inlines ?? []"
        >{{ hidden.text }}</span
      >
    }
    @case ("strong") {
      @let strong = inline | strong;
      <strong
        class="strong"
        [ngClass]="strong.ngClass"
        [attr.id]="strong.id"
        [attr.lang]="strong.lang"
        [attr.title]="strong.title"
        [contentInline]="strong.inlines ?? []"
        >{{ strong.text }}</strong
      >
    }
    @case ("emphasis") {
      @let emphasis = inline | emphasis;
      <em
        class="emphasis"
        [ngClass]="emphasis.ngClass"
        [attr.id]="emphasis.id"
        [attr.lang]="emphasis.lang"
        [attr.title]="emphasis.title"
        [contentInline]="emphasis.inlines ?? []"
        >{{ emphasis.text }}</em
      >
    }
    @case ("abbreviation") {
      @let abbreviation = inline | abbreviation;
      <abbr
        class="abbreviation"
        [ngClass]="abbreviation.ngClass"
        [attr.id]="abbreviation.id"
        [attr.lang]="abbreviation.lang"
        [attr.title]="abbreviation.title"
        [contentInline]="abbreviation.inlines ?? []"
        >{{ abbreviation.text }}</abbr
      >
    }
    @case ("time") {
      @let time = inline | time;
      <time
        class="time"
        [ngClass]="time.ngClass"
        [attr.dateTime]="time.dateTime"
        [attr.id]="time.id"
        [attr.lang]="time.lang"
        [attr.title]="time.title"
        [contentInline]="time.inlines ?? []"
        >{{ time.text }}</time
      >
    }
    @case ("subscript") {
      @let subscript = inline | subscript;
      <sub
        class="subscript"
        [ngClass]="subscript.ngClass"
        [attr.id]="subscript.id"
        [attr.lang]="subscript.lang"
        [attr.title]="subscript.title"
        [contentInline]="subscript.inlines ?? []"
        >{{ subscript.text }}</sub
      >
    }
    @case ("superscript") {
      @let superscript = inline | superscript;
      <sup
        class="superscript"
        [ngClass]="superscript.ngClass"
        [attr.id]="superscript.id"
        [attr.lang]="superscript.lang"
        [attr.title]="superscript.title"
        [contentInline]="superscript.inlines ?? []"
        >{{ superscript.text }}</sup
      >
    }
    @case ("link") {
      @let link = inline | link;
      @switch (link.type) {
        @case ("router") {
          <a
            class="link"
            [ngClass]="['x-small:color:primary'] | joinArray: link.ngClass"
            [routerLink]="link.routerLink"
            [fragment]="link.fragment"
            [queryParams]="link.queryParams"
            [queryParamsHandling]="link.queryParamsHandling"
            [attr.id]="link.id"
            [attr.lang]="link.lang"
            [attr.title]="link.title"
            [contentInline]="link.inlines ?? []"
            >{{ link.text }}</a
          >
        }
        @case ("href") {
          <a
            class="link"
            [ngClass]="['x-small:color:primary'] | joinArray: link.ngClass"
            [attr.href]="link.href"
            [attr.target]="link.target"
            [attr.id]="link.id"
            [attr.lang]="link.lang"
            [attr.title]="link.title"
            [contentInline]="link.inlines ?? []"
            >{{ link.text }}</a
          >
        }
      }
    }
    @case ("anchor") {
      @let anchor = inline | anchor;
      @switch (anchor.type) {
        @case ("router") {
          <a
            class="anchor"
            [ngClass]="['x-small:color:primary'] | joinArray: anchor.ngClass"
            [routerLink]="anchor.routerLink"
            [fragment]="anchor.fragment"
            [queryParams]="anchor.queryParams"
            [queryParamsHandling]="anchor.queryParamsHandling"
            [attr.id]="anchor.id"
            [attr.lang]="anchor.lang"
            [attr.title]="anchor.title"
            [contentInline]="anchor.inlines ?? []"
            >{{ anchor.text }}</a
          >
        }
        @case ("href") {
          <a
            class="anchor"
            [ngClass]="['x-small:color:primary'] | joinArray: anchor.ngClass"
            [attr.href]="anchor.href"
            [attr.target]="anchor.target"
            [attr.id]="anchor.id"
            [attr.lang]="anchor.lang"
            [attr.title]="anchor.title"
            [contentInline]="anchor.inlines ?? []"
            >{{ anchor.text }}</a
          >
        }
      }
    }
    @case ("button") {
      @let button = inline | button;
      @switch (button.type) {
        @case ("router") {
          <a
            class="button"
            [ngClass]="['x-small:color:2-inverse', 'x-small:background-color:primary'] | joinArray: button.ngClass"
            [routerLink]="button.routerLink"
            [fragment]="button.fragment"
            [queryParams]="button.queryParams"
            [queryParamsHandling]="button.queryParamsHandling"
            [attr.id]="button.id"
            [attr.lang]="button.lang"
            [attr.title]="button.title"
            [contentInline]="button.inlines ?? []"
            >{{ button.text }}</a
          >
        }
        @case ("href") {
          <a
            class="button"
            [ngClass]="['x-small:color:2-inverse', 'x-small:background-color:primary'] | joinArray: button.ngClass"
            [attr.href]="button.href"
            [attr.target]="button.target"
            [attr.id]="button.id"
            [attr.lang]="button.lang"
            [attr.title]="button.title"
            [contentInline]="button.inlines ?? []"
            >{{ button.text }}</a
          >
        }
      }
    }
    @case ("image") {
      @let image = inline | image;
      <img
        class="image"
        [ngClass]="image.ngClass"
        [ngSrc]="image.ngSrc"
        [width]="image.width"
        [height]="image.height"
        [priority]="image.priority"
        [placeholder]="image.placeholder"
        [attr.alt]="image.alt"
        [attr.id]="image.id"
        [attr.lang]="image.lang"
        [attr.title]="image.title"
      />
    }
    @case ("logo") {
      @let logo = inline | logo;
      <svg
        class="logo"
        [ngClass]="logo.ngClass"
        [attr.viewBox]="logo.viewBox"
        [attr.xmlns]="'http://www.w3.org/2000/svg'"
        [attr.aria-hidden]="true"
      >
        @for (logoItem of logo.logoItems; track $index) {
          <path [attr.d]="logoItem.d" [attr.fill]="logoItem.fill" />
        }
      </svg>
    }
    @case ("icon") {
      @let icon = inline | icon;
      <svg
        class="icon"
        [ngClass]="icon.ngClass"
        [attr.viewBox]="icon.viewBox"
        [attr.xmlns]="'http://www.w3.org/2000/svg'"
        [attr.aria-hidden]="true"
      >
        @for (iconItem of icon.iconItems; track $index) {
          <path [attr.d]="iconItem.d" [attr.fill]="iconItem.fill" />
        }
      </svg>
    }
    @case ("linebreak") {
      @let linebreak = inline | linebreak;
      <br class="linebreak" [ngClass]="linebreak.ngClass" />
    }
  }
}
<ng-content />

Generating the Content

When the blocks are retrieved from Firestore, they are simply loaded as follows (example):

<article class="article" [contentBlock]="document.blocks"></article>

The Issue

After a number of iterations, SSR stops rendering the HTML, and instead, it outputs a <!--container--> instead, which is simply used for hydration.

What Should Happen

SSR should wait until all HTML is rendered and avoid outputing <!--container--> as this is not good for SEO.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw


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

Angular CLI: 19.2.7
Node: 22.14.0
Package Manager: npm 10.9.2
OS: win32 x64

Angular: 19.2.6
... common, compiler, compiler-cli, core, platform-browser
... platform-browser-dynamic, platform-server, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1902.7
@angular-devkit/build-angular   19.2.7
@angular-devkit/core            19.2.7
@angular-devkit/schematics      19.2.7
@angular/cli                    19.2.7
@angular/fire                   19.1.0
@angular/ssr                    19.2.7
@schematics/angular             19.2.7
rxjs                            7.8.2
typescript                      5.8.3

Anything else?

No response

@JeanMeche
Copy link
Member
JeanMeche commented Apr 15, 2025

Hi, could you please provide a minimal reproduction of your issue via a github repo or a stackblitz, thank you.

@davidbusuttil
Copy link
Author

Hi, could you please provide a minimal reproduction of your issue via a github repo or a stackblitz, thank you.

I am going to try, give me a couple of minutes.

@kirjs kirjs added the area: server Issues related to server-side rendering label Apr 15, 2025
@ngbot ngbot bot added this to the needsTriage milestone Apr 15, 2025
@davidbusuttil
Copy link
Author
davidbusuttil commented Apr 15, 2025

Hi, could you please provide a minimal reproduction of your issue via a github repo or a stackblitz, thank you.

Here is the link to the StackBlitz: https://stackblitz.com/edit/stackblitz-starters-duec5vxj?file=README.md

The libraries have been packed as they are private.

The issue is happening on route /boats/:reference.

Ideally code is downloaded locally and is served so that page source can be viewed when accessing for example /boats/aquador-250-ht. You can check the source and towards the end of the marine-boat-detail component, all that is rendered are <!--container--> tags.

@JeanMeche
Copy link
Member

Sorry but this is not a minimal repro.

If you don't get any content, it could be because your app became stable before the data was loaded.

@davidbusuttil
Copy link
Author

It is happening when a lot of data is being retrieved from Firestore in Firebase.

I have tried everything, even PendingTasks to block the application from getting stable.

Angular is aware of the data fetched from Firestore. This is evident from the <!--container--> being generated for hydration for each node.

I don't think the app is becoming stable... I think it is more of a hard coded timeout on SSR which doesn't allow all the HTML to be generated.

If you want, I can create a StackBlitz of the library concerned.

@thePunderWoman
Copy link
Contributor

If you can convert your reproduction into a truly minimal repro with just the specific case where this occurs, we'd be happy to look more into it.

@AndrewKushnir AndrewKushnir added the needs reproduction This issue needs a reproduction in order for the team to investigate further label Apr 15, 2025
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Apr 15, 2025
@davidbusuttil
Copy link
Author
davidbusuttil commented Apr 15, 2025

I managed to find the problem.

It is with NgOptimizedImage.

I was getting this error below, but I was ignoring it as the content was still being rendered on the client.

ERROR RuntimeError: NG02961: The NgOptimizedImage directive has detected that more than 5 images were marked as priority. This might negatively affect an overall performance of the page. To fix this, remove the "priority" attribute from images with less priority.

This error is marking the app as "stable" and allowing SSR to serialize... <!--container--> is outputted for the rest of the HTML content, which is then hydrated on the client side.

Perhaps this should be a warning rather than an error? My suggestion is that the prefetching is not added to the head on SSR if this occurs, but it shouldn't let the application become stable and serialize...

The same does not happen on the below.

NG02955: The NgOptimizedImage directive (activated on an <img> element with the ngSrc="...") has detected that this image is the Largest Contentful Paint (LCP) element but was not marked "priority". This image should be marked "priority" in order to prioritize its loading. To fix this, add the "priority" attribute.

@alan-agius4 alan-agius4 added area: common Issues related to APIs in the @angular/common package and removed needs reproduction This issue needs a reproduction in order for the team to investigate further labels Apr 16, 2025
@ngbot ngbot bot modified the milestones: Backlog, needsTriage Apr 16, 2025
alan-agius4 added a commit to alan-agius4/angular that referenced this issue Apr 16, 2025
…ge` exceeds the preload limit

This should not be treated as a hard error, as it doesn’t break the application but merely degrades performance.

Closes angular#60871
@davidbusuttil
Copy link
Author

Hi @alan-agius4.

Just saw the commits and wanted to let you know—the issue of serialisation is also occurring on production mode, not only when the error is thrown on development mode.

@alan-agius4
Copy link
Contributor

@davidbusuttil, can you please share the repro again as it appears to have been deleted.

@alan-agius4 alan-agius4 added the needs reproduction This issue needs a reproduction in order for the team to investigate further label Apr 16, 2025
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Apr 16, 2025
alan-agius4 added a commit to alan-agius4/angular that referenced this issue Apr 16, 2025
…ge` exceeds the preload limit

This should not be treated as a hard error, as it doesn’t break the application but merely degrades performance.

Closes angular#60871

(cherry picked from commit 8f25d4a)
@davidbusuttil
Copy link
Author

Hi @alan-agius4.

Here is the issue reproduced: https://stackblitz.com/edit/stackblitz-starters-mtwt34g2.

@alan-agius4
Copy link
Contributor

Hi @davidbusuttil,
I tried the reproduction but wasn’t able to replicate the issue—there’s no comment in the output. Here’s a gist of what I get when running ng s --configuration=production

https://gist.github.com/alan-agius4/3429095c90111a7d8f44ffc34d38c729

@davidbusuttil
Copy link
Author

Thanks @alan-agius4.

I was wrong than. Issue only occurs on development configuration.

If I re-encounter the problem, I will let you know.

Thanks a lot for the help!

@kirjs kirjs closed this as completed in cbbea70 Apr 16, 2025
kirjs pushed a commit that referenced this issue Apr 16, 2025
…ge` exceeds the preload limit (#60883)

This should not be treated as a hard error, as it doesn’t break the application but merely degrades performance.

Closes #60871

(cherry picked from commit 8f25d4a)

PR Close #60883
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators May 17, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: common Issues related to APIs in the @angular/common package area: server Issues related to server-side rendering common: image directive needs reproduction This issue needs a reproduction in order for the team to investigate further
Projects
None yet
6 participants
0