feat(store-header): add fetchpriority and loading attributes to images for performance optimization

This commit is contained in:
2026-09-22 10:02:53 -03:00
parent 32f86d14d6
commit 1f35a6d709
7 changed files with 81 additions and 17 deletions

View File

@@ -90,6 +90,11 @@ describe('App', () => {
expect( expect(
document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'), document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'),
).toBe(tenant.favicon); ).toBe(tenant.favicon);
expect(
document.head
.querySelector<HTMLLinkElement>('link[rel~="icon"]')
?.getAttribute('fetchpriority'),
).toBe('high');
}); });
it('renders the tenant not found screen when the tenant is missing', async () => { it('renders the tenant not found screen when the tenant is missing', async () => {
@@ -114,6 +119,10 @@ describe('App', () => {
expect( expect(
document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'), document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'),
).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E"); ).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E");
expect(
document.head
.querySelector<HTMLLinkElement>('link[rel~="icon"]')
?.getAttribute('fetchpriority'),
).toBe('high');
}); });
}); });

View File

@@ -78,6 +78,7 @@ export class App {
} }
favicon.setAttribute('href', faviconHref); favicon.setAttribute('href', faviconHref);
favicon.setAttribute('fetchpriority', 'high');
}); });
} }

View File

@@ -1,8 +1,16 @@
<header class="immersive-header" [class.immersive-header--compact]="!showHero()"> <header class="immersive-header" [class.immersive-header--compact]="!showHero()">
<div <div class="immersive-header__backdrop">
class="immersive-header__backdrop" @if (showHero() && backgroundImageUrl(); as backgroundImage) {
[style.background-image]="backgroundImageUrl() ? 'url(' + backgroundImageUrl() + ')' : null" <img
></div> class="immersive-header__backdrop-image"
[src]="backgroundImage"
alt=""
loading="eager"
fetchpriority="high"
decoding="async"
/>
}
</div>
<div class="immersive-header__content"> <div class="immersive-header__content">
<nav <nav
@@ -11,7 +19,13 @@
> >
<a routerLink="/" class="immersive-header__brand" aria-label="Ir al inicio"> <a routerLink="/" class="immersive-header__brand" aria-label="Ir al inicio">
@if (logoUrl()) { @if (logoUrl()) {
<img [src]="logoUrl()" alt="Logo del comercio" /> <img
[src]="logoUrl()"
alt="Logo del comercio"
loading="eager"
fetchpriority="high"
decoding="async"
/>
} }
</a> </a>

View File

@@ -25,9 +25,19 @@
grid-area: 1 / 1; grid-area: 1 / 1;
position: relative; position: relative;
z-index: 0; z-index: 0;
overflow: hidden;
background-color: #101010; background-color: #101010;
background-position: center 42%; }
background-size: cover;
.immersive-header__backdrop-image {
position: absolute;
inset: 0;
z-index: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 42%;
} }
.immersive-header__backdrop::before, .immersive-header__backdrop::before,
@@ -35,6 +45,8 @@
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 1;
pointer-events: none;
} }
.immersive-header__backdrop::before { .immersive-header__backdrop::before {

View File

@@ -13,7 +13,14 @@
aria-label="Logo del comercio" aria-label="Logo del comercio"
> >
@if (logoUrl()) { @if (logoUrl()) {
<img class="store-layout__brand-logo" [src]="logoUrl()" alt="Logo del comercio" /> <img
class="store-layout__brand-logo"
[src]="logoUrl()"
alt="Logo del comercio"
loading="eager"
fetchpriority="high"
decoding="async"
/>
} @else { } @else {
<div class="store-layout__brand-logo-shell" aria-hidden="true"> <div class="store-layout__brand-logo-shell" aria-hidden="true">
<span class="store-layout__brand-logo-skeleton"></span> <span class="store-layout__brand-logo-skeleton"></span>

View File

@@ -281,10 +281,27 @@ describe('StoreLayoutComponent', () => {
expect(element.querySelector('.immersive-header__description')?.textContent).toContain( expect(element.querySelector('.immersive-header__description')?.textContent).toContain(
'Reservá tu entrada', 'Reservá tu entrada',
); );
expect(element.querySelectorAll('.continuous-carousel__set')).toHaveLength(2); const backdropImage = element.querySelector<HTMLImageElement>(
expect(element.querySelector('.continuous-carousel__image')?.getAttribute('src')).toBe( '.immersive-header__backdrop-image',
'/first.jpg',
); );
expect(backdropImage?.getAttribute('src')).toBe(tenant.header_bg_image);
expect(backdropImage?.getAttribute('loading')).toBe('eager');
expect(backdropImage?.getAttribute('fetchpriority')).toBe('high');
expect(backdropImage?.getAttribute('decoding')).toBe('async');
const headerLogo = element.querySelector<HTMLImageElement>('.immersive-header__brand img');
expect(headerLogo?.getAttribute('loading')).toBe('eager');
expect(headerLogo?.getAttribute('fetchpriority')).toBe('high');
expect(headerLogo?.getAttribute('decoding')).toBe('async');
expect(element.querySelectorAll('.continuous-carousel__set')).toHaveLength(2);
const carouselImages = element.querySelectorAll<HTMLImageElement>(
'.continuous-carousel__image',
);
expect(carouselImages[0]?.getAttribute('src')).toBe('/first.jpg');
expect(carouselImages[0]?.getAttribute('loading')).toBe('eager');
expect(carouselImages[0]?.getAttribute('fetchpriority')).toBe('low');
expect(carouselImages[1]?.getAttribute('fetchpriority')).toBe('low');
expect(carouselImages[5]?.getAttribute('loading')).toBe('lazy');
expect(carouselImages[5]?.getAttribute('fetchpriority')).toBe('low');
expect(element.querySelector('app-cart-icon')).not.toBeNull(); expect(element.querySelector('app-cart-icon')).not.toBeNull();
}); });
@@ -487,11 +504,13 @@ describe('StoreLayoutComponent', () => {
expect(compiled.querySelector('[data-testid="store-header-brand-slot"]')).not.toBeNull(); expect(compiled.querySelector('[data-testid="store-header-brand-slot"]')).not.toBeNull();
expect(compiled.querySelector('[data-testid="store-footer-brand-slot"]')).not.toBeNull(); expect(compiled.querySelector('[data-testid="store-footer-brand-slot"]')).not.toBeNull();
expect( const headerLogo = compiled.querySelector<HTMLImageElement>(
compiled.querySelector( `app-store-header img.store-layout__brand-logo[src="${tenant.header_logo}"]`,
`app-store-header img.store-layout__brand-logo[src="${tenant.header_logo}"]`, );
), expect(headerLogo).not.toBeNull();
).not.toBeNull(); expect(headerLogo?.getAttribute('loading')).toBe('eager');
expect(headerLogo?.getAttribute('fetchpriority')).toBe('high');
expect(headerLogo?.getAttribute('decoding')).toBe('async');
expect( expect(
compiled.querySelector( compiled.querySelector(
`app-store-footer img.store-layout__brand-logo[src="${tenant.footer_logo}"]`, `app-store-footer img.store-layout__brand-logo[src="${tenant.footer_logo}"]`,

View File

@@ -9,6 +9,8 @@
[src]="image" [src]="image"
alt="" alt=""
[loading]="set === 0 && $index < 5 ? 'eager' : 'lazy'" [loading]="set === 0 && $index < 5 ? 'eager' : 'lazy'"
fetchpriority="low"
decoding="async"
/> />
} }
</div> </div>