skip to content

In Angular, a page's hero banner image is its Largest Contentful Paint element; what does adding priority to its NgOptimizedImage tag change?

level: middleimportance: must knowfreq 46%

answer

  1. the one image not to lazy-load
  2. three attributes flip
  3. a preload link during SSR
  4. dev error when LCP lacks it

basics

~20 s

Adding priority to an Angular NgOptimizedImage switches it to loading=eager, fetchpriority=high and decoding=sync, adds a preload link when rendered on the server, and checks for a preconnect. Without it, a hero LCP image is lazy by default and logs a dev error.

solid answer

~40 s

Without `priority`, the hero is a normal `NgOptimizedImage`: `loading="lazy"`, `fetchpriority="auto"`. For the page's LCP element that is the worst combination, because the browser waits for layout before fetching it and gives it no boost. With `priority`, the directive sets `loading="eager"`, `fetchpriority="high"` and `decoding="sync"`; during server-side rendering it also adds a `<link rel="preload" as="image">` to the document head so the fetch starts before the app boots; and in development it checks that the image's origin has a preconnect link. In development, Angular also watches for the LCP element and logs NG02955 if it is an `NgOptimizedImage` without `priority`. Mark only the likely LCP images — the directive warns when more than ten are marked.

code

ts · 16 lines
ts
import { Component } from '@angular/core';
import { NgOptimizedImage } from '@angular/common';

@Component({
  selector: 'app-home-hero',
  imports: [NgOptimizedImage],
  styles: `.hero { position: relative; height: 60vh; }
            .hero img { object-fit: cover; }`,
  template: `
    <section class="hero">
      <img ngSrc="hero/spring-sale.jpg" fill priority sizes="100vw" alt="Spring sale" />
      <h1>Spring sale</h1>
    </section>
  `,
})
export class HomeHero {}

go deeper

for a junior

Know to mark the page's LCP image with priority and that other images are lazy by default.

for a middle

List what priority changes (eager, high fetchpriority, sync decoding, SSR preload, preconnect check) and why lazy loading hurts the LCP image.

for a senior

Find the LCP element per viewport, fix NG02955 and NG02956 warnings, and move background-image heroes to fill images.

for a principal

Own a per-page LCP image policy so priority stays reserved for the few images that define it.

## Why the hero banner is special **Largest Contentful Paint (LCP)** reports when the largest image or text block in the viewport is painted. On a marketing or landing page the hero banner is usually that element, so how early it downloads sets the LCP time. Angular's **`NgOptimizedImage`** lazy-loads images by default — right for most images, wrong for this one. ## What `priority` changes ```html <img ngSrc="hero/spring-sale.jpg" width="1600" height="600" priority alt="Spring sale" /> ``` | | Without `priority` | With `priority` | |---|---|---| | `loading` | `lazy` | `eager` | | `fetchpriority` | `auto` | `high` | | `decoding` | `auto` | `sync` | | Preload link (SSR) | none | `<link rel="preload" as="image">` in the head, with `imagesrcset`/`imagesizes` when set | | Preconnect check (dev) | none | warns (**NG02956**) if the image origin has no `<link rel="preconnect">` | - **`loading="eager"`** removes the wait for layout that lazy loading implies. - **`fetchpriority="high"`** asks the browser to schedule the request ahead of other images. - **`decoding="sync"`** asks the browser to decode it in step with painting rather than deferring it (the `decoding` input exists since v20.1). - The **preload link** exists only when rendering on the server: the HTML reaches the browser with the image hint in `<head>`, so the fetch starts before any JavaScript runs. - **Preconnect** is generated automatically only when a built-in loader's base URL is a literal; otherwise add `<link rel="preconnect">` to `index.html`. ## How Angular tells you it is missing In development mode the directive registers each image with an observer of LCP entries. If the LCP element turns out to be an `NgOptimizedImage` **without** `priority`, Angular logs **NG02955** as a console error: the image "is the Largest Contentful Paint (LCP) element but was not marked priority". It also warns (**NG02964**) when the LCP image's `ngSrc` is changed after it loads, because swapping the source of the LCP image slows it. ## Do not mark everything `priority` is a scarce signal: 1. The browser can only prioritise a few requests; many "high" images compete with each other and with CSS and scripts. 2. In the browser, the directive warns (**NG02966**) when more than **10** images are marked `priority`. 3. During server rendering, it warns (**NG02961**) once more than **5** preload links have been created. The LCP element can differ by viewport (a hero on desktop, a product photo on mobile), so a page may legitimately have two or three `priority` images — not twenty. ## Background images and other traps - A hero set with CSS `background-image` gets none of this. Move it into a child `<img ngSrc … fill priority>` inside the positioned banner element. - A hero inside a `@defer` block is not in the initial render at all; do not defer above-the-fold content. - A hero whose `ngSrc` comes from data loaded after bootstrap cannot be preloaded by the server if the URL is not known at render time. ## A complete hero ```html <section class="hero"> <img ngSrc="hero/spring-sale.jpg" fill priority sizes="100vw" alt="Spring sale" /> <h1>Spring sale</h1> </section> ``` `fill` covers the positioned section, `sizes="100vw"` gives the loader a full-width srcset, and `priority` makes it the one image the browser fetches first.

  • Why does the preload link only appear with server-side rendering?
    In a client-only app, the image element is created by JavaScript; by then the browser has already parsed the document, so a preload link added at that point gains little. With SSR, the directive runs on the server and puts the preload link into the HTML head, so the browser starts fetching before any script runs.
  • The LCP image differs between mobile and desktop. What do you mark?
    Mark each image that is the LCP element on some common viewport — often the desktop hero and the mobile lead image. That is two or three `priority` images, well within the directive's limits; marking every above-the-fold image dilutes the signal.
  • Why does Angular warn when the LCP image's ngSrc changes after load?
    Changing `ngSrc` makes the browser fetch a new file for the element that defines LCP, so the metric waits for the second download. The directive logs NG02964 in development to flag that; keep the LCP image's source stable once rendered.

saying these in an interview costs you the question

  • priority only adds fetchpriority=high; loading stays lazy.
  • priority always injects a preload link, even without SSR.
  • Marking every above-the-fold image priority makes the page faster.
  • The missing-priority check runs in production and reports to analytics.
  • A CSS background-image hero can be marked priority.