CLS Issues on Salesforce Commerce Cloud | Blue Frog Docs

CLS Issues on Salesforce Commerce Cloud

Fixing Cumulative Layout Shift issues on SFCC

CLS Issues on Salesforce Commerce Cloud

General Guide: See Global CLS Guide for universal concepts and fixes.

SFCC-Specific CLS Causes

1. Dynamic Content Slots

Content slots loading after page render:

<!-- Problem: Slot content causes layout shift -->
<div class="promo-area">
    <isslot id="home-promo" context="global" description="Homepage Promo"/>
</div>

2. Einstein Recommendations

Einstein widgets injecting content dynamically:

<!-- Einstein recommendations load async -->
<div class="einstein-recs" data-einstein-type="recently-viewed"></div>

3. Lazy-Loaded Product Images

Images without explicit dimensions:

<!-- Missing dimensions cause shifts -->
<img src="${product.image.url}" loading="lazy" alt="${product.name}">

4. Price Updates via AJAX

Pricing updates based on customer group or promotions:

// Dynamic price loading
$.get('/Pricing-GetPrice', function(data) {
    $('.price-display').html(data.formattedPrice); // Causes shift
});

SFCC-Specific Fixes

Fix 1: Reserve Space for Content Slots

Add minimum height or aspect ratio:

/* Reserve space for promo slot */
.promo-area {
    min-height: 200px;
}

/* Or use aspect ratio */
.promo-area {
    aspect-ratio: 16/9;
    width: 100%;
}
<div class="promo-area" style="min-height: 200px;">
    <isslot id="home-promo" context="global" description="Homepage Promo"/>
</div>

Fix 2: Pre-render Einstein Placeholders

Use skeleton screens for recommendations:

<div class="einstein-recs" data-einstein-type="recently-viewed">
    <!-- Skeleton placeholder -->
    <div class="recommendation-skeleton">
        <div class="skeleton-item"></div>
        <div class="skeleton-item"></div>
        <div class="skeleton-item"></div>
        <div class="skeleton-item"></div>
    </div>
</div>

<style>
.recommendation-skeleton {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
    min-height: 300px;
}
.skeleton-item {
    background: #f0f0f0;
    border-radius: 8px;
    animation: pulse 1.5s infinite;
}
</style>

Fix 3: Set Image Dimensions

Always specify width and height in ISML:

<isscript>
    var imgWidth = 400;
    var imgHeight = 400;
</isscript>

<img
    src="${product.getImages('medium')[0].getURL()}?sw=${imgWidth}&sh=${imgHeight}"
    width="${imgWidth}"
    height="${imgHeight}"
    alt="${product.name}"
    loading="lazy"
>

For responsive images, use CSS aspect-ratio:

.product-image {
    aspect-ratio: 1/1;
    width: 100%;
    height: auto;
    object-fit: cover;
}

Fix 4: Inline Initial Price Display

Render prices server-side initially:

<!-- Server-rendered price (no shift) -->
<span class="price-display" data-product-id="${product.ID}">
    <isprint value="${product.price.sales.formatted}"/>
</span>

<script>
// Only update if different from server value
$(document).ready(function() {
    $('.price-display').each(function() {
        var $el = $(this);
        var productId = $el.data('product-id');
        var serverPrice = $el.text().trim();

        $.get('/Pricing-GetPrice?pid=' + productId, function(data) {
            if (data.formattedPrice !== serverPrice) {
                // Use transform to avoid layout shift
                $el.css('transform', 'scale(1.05)');
                $el.html(data.formattedPrice);
                setTimeout(function() {
                    $el.css('transform', 'scale(1)');
                }, 200);
            }
        });
    });
});
</script>

Fix 5: Stabilize Header and Navigation

Prevent header shifts from login state:

<header class="site-header" style="height: 80px;">
    <div class="user-account">
        <isif condition="${customer.authenticated}">
            <span class="welcome">Welcome, ${customer.firstName}</span>
        <iselse>
            <a href="${URLUtils.url('Login-Show')}">Sign In</a>
        </isif>
    </div>
</header>

<style>
.site-header {
    height: 80px; /* Fixed height prevents shift */
}
.user-account {
    min-width: 150px; /* Reserve space for content */
}
</style>

Fix 6: Handle Web Fonts Properly

Prevent font-swap layout shifts:

/* Use font-display: optional for critical text */
@font-face {
    font-family: 'StorefrontFont';
    src: url('/fonts/storefront.woff2') format('woff2');
    font-display: optional;
}

/* Fallback with similar metrics */
body {
    font-family: 'StorefrontFont', -apple-system, BlinkMacSystemFont, sans-serif;
}

Monitoring CLS on SFCC

// Track CLS with web-vitals
import {onCLS} from 'web-vitals';

onCLS(function(metric) {
    dataLayer.push({
        'event': 'web_vitals',
        'vital_name': 'CLS',
        'vital_value': metric.value,
        'vital_rating': metric.rating
    });
});

CLS Targets

Rating CLS Value
Good ≤ 0.1
Needs Improvement 0.1 - 0.25
Poor > 0.25

Next Steps

// SYS.FOOTER