Fix BigCommerce CLS (Cumulative Layout Shift) Issues
Cumulative Layout Shift (CLS) measures visual stability - how much content shifts unexpectedly during page load. Good CLS scores improve user experience and SEO rankings.
Understanding CLS
What is CLS?
CLS measures unexpected layout shifts that occur during the entire lifespan of a page. Every time a visible element changes position from one frame to the next, a layout shift occurs.
Google's CLS Thresholds:
- Good: Less than 0.1
- Needs Improvement: 0.1 - 0.25
- Poor: Greater than 0.25
Common CLS Causes on BigCommerce
- Images without dimensions: Images load and push content down
- Web fonts (FOUT/FOIT): Font loading causing text reflow
- Dynamically injected content: Banners, alerts, or promotional messages appearing
- Ads and embeds: Third-party content loading late
- Animations: Slideshow transitions, product carousels
- Cart drawer/overlay: Fly-out elements affecting layout
Diagnosing CLS Issues on BigCommerce
Step 1: Identify Layout Shifts
Using PageSpeed Insights:
- Visit PageSpeed Insights
- Enter your BigCommerce store URL
- Click Analyze
- Scroll to Diagnostics > Avoid large layout shifts
- Review which elements are causing shifts
Using Chrome DevTools:
- Open Chrome DevTools (F12)
- Click More tools > Rendering
- Enable Layout Shift Regions
- Reload page
- Blue highlights indicate layout shifts
Using Web Vitals Chrome Extension:
- Install Web Vitals Extension
- Visit your store
- Click extension icon
- View CLS score and click for details
Step 2: Measure Current CLS
Field Data (Real Users):
- Google Search Console > Core Web Vitals
- Chrome User Experience Report (CrUX)
- GA4 with Web Vitals integration
Lab Data (Simulated):
- PageSpeed Insights
- Chrome DevTools Lighthouse
- WebPageTest
Step 3: Identify Specific Shift Sources
Common shift sources on BigCommerce:
- Product images without explicit dimensions
- Hero carousels with different image heights
- Category banners loading late
- Promotional alerts/bars injected dynamically
- Third-party apps (reviews, chat widgets)
- Web fonts causing text reflow
- Cart/search overlays affecting layout
BigCommerce-Specific CLS Fixes
1. Add Image Dimensions to All Images
The most common CLS cause on BigCommerce is images without width and height attributes.
Fix Hero Carousel Images
File: templates/components/common/carousel.html
{{#each slides}}
<div class="slide">
<img src="{{image.data}}"
alt="{{image.alt}}"
width="1920"
height="600"
{{!-- Explicit dimensions prevent layout shift --}}
loading="{{#if @first}}eager{{else}}lazy{{/if}}">
</div>
{{/each}}
Key Point: Even with loading="lazy", images MUST have width and height attributes.
Fix Product Grid Images
File: templates/components/products/card.html
<div class="card">
<figure class="card-figure">
<img src="{{getImage image 'productgallery' '300w'}}"
alt="{{image.alt}}"
width="300"
height="300"
loading="lazy"
{{!-- 1:1 aspect ratio for consistency --}}>
</figure>
<div class="card-body">
<h3 class="card-title">{{name}}</h3>
<div class="card-price">{{price}}</div>
</div>
</div>
Product Image Best Practices:
- Use consistent aspect ratios (1:1 or 4:3)
- Add explicit
widthandheight - Use CSS to make images responsive
CSS for Responsive Images:
.card-figure img {
width: 100%;
height: auto;
aspect-ratio: 1 / 1; /* Maintains square aspect ratio */
}
Fix Product Page Main Images
File: templates/components/products/product-view.html
<div class="productView-images">
{{#each images}}
<div class="productView-image">
<img src="{{getImage this 'product' '1280w'}}"
alt="{{data}}"
width="1280"
height="1280"
{{!-- First image loads eagerly, others lazy --}}
loading="{{#if @first}}eager{{else}}lazy{{/if}}">
</div>
{{/each}}
</div>
2. Reserve Space for Dynamic Content
Promotional Banners
File: templates/components/common/alert.html or promotional banner component
Problem:
<!-- Banner loads late and pushes content down -->
<div class="promo-banner">
Free shipping on orders over $50!
</div>
Solution:
<!-- Reserve space with min-height -->
<div class="promo-banner" style="min-height: 40px;">
Free shipping on orders over $50!
</div>
Better CSS Approach:
.promo-banner {
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
Cart Item Count Badge
Problem: Badge appears after page load, shifting layout.
Solution:
<a href="/cart" class="cart-link">
Cart
<span class="cart-badge" data-cart-quantity>
{{#if cart.quantity}}
{{cart.quantity}}
{{else}}
0
{{/if}}
</span>
</a>
CSS:
.cart-badge {
display: inline-block;
min-width: 20px; /* Reserve minimum space */
height: 20px;
line-height: 20px;
text-align: center;
}
3. Optimize Web Font Loading
Web fonts can cause significant layout shift when they load.
Use font-display: swap
File: assets/scss/settings/global/_typography.scss or CSS
@font-face {
font-family: 'MyCustomFont';
src: url('../fonts/MyCustomFont.woff2') format('woff2');
font-display: swap; /* Show fallback font immediately, swap when loaded */
font-weight: normal;
font-style: normal;
}
Font Display Options:
swap: Shows fallback text immediately (best for CLS)optional: Uses custom font only if cached (minimal CLS)block: Invisible text flash (causes CLS, avoid)
Preload Critical Fonts
File: templates/layout/base.html
<head>
{{!-- Preload critical fonts --}}
<link rel="preload"
href="{{cdn 'assets/fonts/main-font.woff2'}}"
as="font"
type="font/woff2"
crossorigin>
{{!-- Use system fonts as fallback --}}
<style>
body {
font-family: 'MyCustomFont', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
</style>
</head>
Match Fallback Font Metrics
Use fallback fonts with similar metrics to reduce shift:
@font-face {
font-family: 'MyCustomFont';
src: url('../fonts/MyCustomFont.woff2') format('woff2');
font-display: swap;
ascent-override: 95%; /* Match custom font metrics */
descent-override: 25%;
line-gap-override: 0%;
size-adjust: 100%;
}
4. Fix Stencil Carousel Layout Shifts
BigCommerce carousel components often cause CLS.
Option 1: Fixed Height Carousel
File: templates/components/common/carousel.html
<div class="carousel" style="height: 600px;">
{{!-- Fixed height prevents shift --}}
{{#each slides}}
<div class="slide">
<img src="{{image.data}}"
alt="{{image.alt}}"
width="1920"
height="600"
loading="{{#if @first}}eager{{else}}lazy{{/if}}">
</div>
{{/each}}
</div>
CSS:
.carousel {
position: relative;
width: 100%;
height: 600px; /* Fixed height */
overflow: hidden;
}
.carousel .slide img {
width: 100%;
height: 100%;
object-fit: cover; /* Maintain aspect ratio */
}
Option 2: Aspect Ratio Box
CSS:
.carousel {
position: relative;
width: 100%;
aspect-ratio: 16 / 9; /* Or your desired ratio */
overflow: hidden;
}
.carousel .slide {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
5. Control Third-Party App Layout Shifts
BigCommerce apps can inject content that causes layout shifts.
Reserve Space for Review Widgets
If using a review app (Yotpo, Stamped.io, etc.):
File: templates/pages/product.html
<div class="product-reviews" style="min-height: 200px;">
{{!-- Reviews will load here --}}
<div id="review-widget"></div>
</div>
CSS:
.product-reviews {
min-height: 200px; /* Reserve space for reviews */
transition: min-height 0.3s ease; /* Smooth transition */
}
.product-reviews.loaded {
min-height: auto; /* Remove reservation after load */
}
Load Chat Widgets After Page Load
File: Script Manager or templates/layout/base.html
<script>
// Load chat widget after page load to prevent CLS
window.addEventListener('load', function() {
setTimeout(function() {
// Initialize chat widget
(function() {
// Chat widget code here
})();
}, 1000); // Delay 1 second after load
});
</script>
6. Fix Sticky Header Layout Shifts
Sticky headers can cause layout shift if not implemented correctly.
Reserve Space for Sticky Header
CSS:
/* Fixed height header */
.header {
position: sticky;
top: 0;
height: 80px; /* Explicit height */
z-index: 100;
}
/* Prevent content jump */
body {
padding-top: 80px; /* Match header height */
}
Alternative: Use CSS Transform
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
transform: translateZ(0); /* GPU acceleration, smoother */
will-change: transform;
}
body {
padding-top: 80px;
}
7. Fix Alert/Notification Bar Shifts
Promotional alert bars that appear/disappear cause CLS.
Always Reserve Space
File: templates/components/common/alert.html
<div class="alert-container" style="min-height: {{#if alert.message}}40px{{else}}0{{/if}};">
{{#if alert.message}}
<div class="alert">
{{alert.message}}
</div>
{{/if}}
</div>
Better Approach: Always Render, Use Visibility
<div class="alert-container">
<div class="alert {{#unless alert.message}}alert--hidden{{/unless}}">
{{#if alert.message}}
{{alert.message}}
{{else}}
{{!-- Maintain height even when hidden --}}
{{/if}}
</div>
</div>
CSS:
.alert-container {
min-height: 40px;
}
.alert {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s ease;
}
.alert--hidden {
opacity: 0;
pointer-events: none;
/* Still takes up space, no layout shift */
}
8. Optimize Category Page Grid Layout
Use CSS Grid with Fixed Rows
File: templates/pages/category.html
CSS:
.productGrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
grid-auto-rows: 400px; /* Fixed row height prevents shifts */
}
.productCard {
display: flex;
flex-direction: column;
height: 100%;
}
.productCard-image {
flex-shrink: 0;
height: 250px; /* Fixed image area */
overflow: hidden;
}
.productCard-body {
flex-grow: 1;
padding: 15px;
}
Advanced CLS Optimization
Use content-visibility CSS Property
Modern CSS property to improve rendering performance and reduce CLS:
.product-description,
.product-reviews,
.related-products {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Estimated height */
}
Benefits:
- Browser skips rendering off-screen content
- Reserves estimated space to prevent shift
- Improves overall page performance
Implement Skeleton Screens
Show placeholder content while actual content loads:
CSS:
.product-skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: loading 1.5s ease-in-out infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.product-card.loading .card-image {
width: 100%;
height: 300px; /* Reserve exact space */
}
Use Intersection Observer for Lazy Loading
Implement custom lazy loading with reserved space:
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
document.querySelectorAll('img.lazy').forEach(img => {
imageObserver.observe(img);
});
HTML:
<img class="lazy"
data-src="actual-image.jpg"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3C/svg%3E"
width="300"
height="300"
alt="Product">
Testing and Validation
Measure CLS Improvements
Baseline Measurement:
- Run PageSpeed Insights 3 times
- Average CLS scores
- Note which elements cause shifts
Apply Fixes
Retest:
- Clear all caches
- Wait 10 minutes
- Run PageSpeed Insights 3 times
- Average new CLS scores
- Verify shifts are eliminated
Real User Monitoring
Monitor CLS in Google Search Console:
- Core Web Vitals Report
- Filter by CLS metric
- Review "Poor" URLs
- Track improvements over 28-day periods
Debug Specific Shifts
Use Layout Shift Regions in Chrome:
- DevTools > Rendering
- Enable "Layout Shift Regions"
- Reload page and scroll
- Blue highlights show where shifts occur
- Identify and fix each shift
Common CLS Pitfalls on BigCommerce
❌ Missing Image Dimensions
Problem:
<img src="product.jpg" alt="Product"> <!-- No dimensions! -->
Solution:
<img src="product.jpg" alt="Product" width="300" height="300">
❌ Dynamic Content Without Reserved Space
Problem:
<div class="promo-banner"> <!-- Appears late, causes shift -->
Sale ends tonight!
</div>
Solution:
<div class="promo-banner" style="min-height: 40px;">
Sale ends tonight!
</div>
❌ Inconsistent Carousel Image Heights
Problem:
<!-- Different image sizes cause shifts between slides -->
<img src="banner1.jpg" height="400">
<img src="banner2.jpg" height="600"> <!-- Different height! -->
Solution:
.carousel {
height: 600px; /* Fixed height container */
}
.carousel img {
width: 100%;
height: 100%;
object-fit: cover; /* Crop to fit */
}
❌ Web Fonts Without font-display
Problem:
@font-face {
font-family: 'Custom';
src: url('font.woff2');
/* Missing font-display! */
}
Solution:
@font-face {
font-family: 'Custom';
src: url('font.woff2');
font-display: swap; /* Prevent invisible text */
}
CLS Optimization Checklist
- Add
widthandheightto all images - Use
aspect-ratioCSS for responsive images - Add
font-display: swapto all @font-face rules - Preload critical fonts
- Reserve space for dynamic content (alerts, banners)
- Fix carousel with consistent heights or aspect ratios
- Delay third-party widgets (chat, reviews) until after load
- Use fixed or sticky positioning correctly
- Test with "Layout Shift Regions" in Chrome DevTools
- Verify CLS score in PageSpeed Insights
- Monitor real-user CLS in Google Search Console
Expected Results
Well-optimized BigCommerce stores should achieve:
- Mobile CLS: 0.05 - 0.10
- Desktop CLS: 0.02 - 0.08
- Good CWV Status: 75%+ of URLs passing CLS threshold
Typical improvements:
- 60-80% CLS reduction from image dimensions
- 20-40% reduction from font optimization
- 15-30% reduction from reserved space for dynamic content
When to Seek Help
Contact BigCommerce support or hire a developer if:
- CLS remains above 0.25 after optimizations
- Third-party apps cause unavoidable shifts
- Complex custom theme code needs refactoring
- Need advanced CSS Grid/Flexbox implementation