General Guide: See CLS Overview for universal concepts.
What is Cumulative Layout Shift (CLS)?
Cumulative Layout Shift measures visual stability by tracking unexpected layout shifts during page load. A CLS score under 0.1 is considered good, while scores above 0.25 are poor.
Why CLS matters for e-commerce:
- Reduces user frustration (clicking wrong buttons)
- Improves conversion rates
- Critical Core Web Vital for SEO rankings
- Affects Google search rankings
- Impacts user trust and purchase decisions
Volusion-Specific CLS Causes
Product Images Without Dimensions
Problem: Images load without predefined dimensions, causing layout shifts
Common scenarios:
- Product thumbnail grids
- Product detail page main images
- Category page listing images
- Cart preview images
- Related product carousels
Impact on CLS:
Before image loads: Element height = 0px
After image loads: Element height = 300px
Result: 300px layout shift affecting all content below
Dynamic Pricing and Inventory Updates
Problem: AJAX-loaded prices and stock info shift layout
Examples:
- "Was $99, Now $79" pricing appearing after page load
- "Only 3 left in stock" messages
- Volume discount badges
- Member-only pricing reveals
- Real-time inventory counts
Impact:
- Price elements expand/contract
- Sale badges push content
- Stock warnings add extra lines
- Discount messages change element heights
Late-Loading Promotional Banners
Problem: Banner content loads after initial render
Common on Volusion:
- Homepage hero sliders
- Category page promotional bars
- Seasonal sale announcements
- Cookie consent banners
- Email signup popups
- Exit-intent overlays
CLS scenarios:
- Banner appears pushing content down
- Multi-slide carousel shifts during slide changes
- Promo bars expand/collapse
- Sticky headers change height
Cart and Checkout Elements
Problem: Dynamic cart updates cause shifts
Examples:
- Mini-cart flyout animations
- Cart item count badges
- Shipping calculator results
- Coupon code success messages
- Estimated total calculations
- Tax/shipping fee additions
Measuring CLS on Volusion
Using PageSpeed Insights
Test your store:
- Visit PageSpeed Insights
- Enter your Volusion store URL
- Wait for analysis
- Check CLS score under "Core Web Vitals"
What to look for:
Cumulative Layout Shift: 0.089 (Good)
- Acceptable: 0 - 0.1 (Green)
- Needs Improvement: 0.1 - 0.25 (Orange)
- Poor: > 0.25 (Red)
View shift details:
- Click "View Treemap" under CLS
- See which elements cause the most shift
- Identify percentage of viewport affected
Using Chrome DevTools
Enable Layout Shift Regions:
- Open Chrome DevTools (F12)
- Press Cmd/Ctrl + Shift + P
- Type "Show Layout Shift Regions"
- Enable the option
- Refresh page
What you'll see:
- Blue highlights where shifts occur
- Duration and intensity of shifts
- Timing of shift events
Measure with Performance tab:
// Run in Console
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue;
console.log('Layout shift:', entry.value);
console.log('Affected nodes:', entry.sources);
}
}).observe({type: 'layout-shift', buffered: true});
Using Lighthouse
Run Lighthouse audit:
# CLI method
npx lighthouse https://yourstore.com --view --only-categories=performance
# Or use Chrome DevTools Lighthouse tab
Check CLS in report:
- Look for "Cumulative Layout Shift" metric
- Review "Diagnostics" section
- See screenshots of shift moments
- View filmstrip timeline
Real User Monitoring
Track CLS for actual users:
// Add to Volusion template
<script>
new PerformanceObserver((entryList) => {
let clsValue = 0;
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
// Send to analytics
if (typeof gtag !== 'undefined' && clsValue > 0) {
gtag('event', 'CLS', {
event_category: 'Web Vitals',
event_label: 'CLS',
value: Math.round(clsValue * 1000),
non_interaction: true,
});
}
}).observe({type: 'layout-shift', buffered: true});
</script>
Fixing CLS Issues on Volusion
1. Set Image Dimensions
Product Images in Templates
Bad - No dimensions:
<!-- Volusion default template -->
<img src="product-image.jpg" alt="Product">
Good - With dimensions:
<img src="product-image.jpg"
width="400"
height="400"
alt="Product">
For responsive images:
<!-- Maintain aspect ratio -->
<div style="aspect-ratio: 1/1; width: 100%; max-width: 400px;">
<img src="product-image.jpg"
style="width: 100%; height: 100%; object-fit: cover;"
alt="Product">
</div>
CSS approach:
/* In custom CSS */
.product-image-container {
position: relative;
width: 100%;
padding-bottom: 100%; /* 1:1 aspect ratio */
overflow: hidden;
}
.product-image-container img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
<!-- In template -->
<div class="product-image-container">
<img src="product-image.jpg" alt="Product">
</div>
Category Grid Layouts
Standardize product grid:
/* Ensure consistent grid item heights */
.product-grid-item {
display: flex;
flex-direction: column;
min-height: 450px; /* Reserve space */
}
.product-grid-item .image-wrapper {
aspect-ratio: 1/1;
width: 100%;
background: #f0f0f0; /* Placeholder color */
}
.product-grid-item .product-info {
flex-grow: 1;
padding: 15px;
min-height: 120px; /* Reserve for title + price */
}
Cart Thumbnails
Reserve space for cart images:
.cart-item-image {
width: 80px;
height: 80px;
flex-shrink: 0; /* Prevent size changes */
background: #f5f5f5;
}
.cart-item-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
2. Stabilize Dynamic Content
Price Display Areas
Reserve space for pricing:
/* Price container */
.product-price {
min-height: 30px;
display: flex;
align-items: center;
gap: 10px;
}
/* Sale price scenario */
.product-price.on-sale {
min-height: 50px; /* Account for strikethrough price */
}
Handle dynamic pricing:
<!-- Reserve space before AJAX loads -->
<div class="product-price" style="min-height: 30px;">
<span class="price-loading" style="width: 80px; height: 24px; background: #e0e0e0; display: inline-block;"></span>
</div>
<script>
// When price loads, replace placeholder
fetch('/api/get-price?id=123')
.then(res => res.json())
.then(data => {
document.querySelector('.price-loading').outerHTML = `<span class="price">$${data.price}</span>`;
});
</script>
Promotional Banners
Pre-allocate banner space:
<!-- Reserve exact height -->
<div id="promo-banner" style="min-height: 60px; background: #f9f9f9;">
<!-- Banner loads here -->
</div>
// Load banner without shift
function loadPromoBanner() {
const banner = document.getElementById('promo-banner');
const bannerHeight = banner.offsetHeight;
fetch('/api/get-promo')
.then(res => res.text())
.then(html => {
banner.innerHTML = html;
// Maintain height if needed
if (banner.offsetHeight !== bannerHeight) {
banner.style.minHeight = bannerHeight + 'px';
}
});
}
Stock Status Messages
Fixed height for inventory messages:
.stock-status {
min-height: 24px;
margin: 10px 0;
}
.stock-status.in-stock::before {
content: "✓ ";
color: green;
}
.stock-status.low-stock::before {
content: "⚠ ";
color: orange;
}
<div class="stock-status in-stock">
In Stock - Ships Today
</div>
3. Control Animations and Overlays
Cart Flyout
Prevent shift with fixed positioning:
/* Cart overlay - no layout shift */
.cart-flyout {
position: fixed;
top: 0;
right: -400px; /* Off-screen initially */
width: 400px;
height: 100vh;
background: white;
transition: right 0.3s ease;
z-index: 1000;
}
.cart-flyout.active {
right: 0;
}
/* Backdrop */
.cart-flyout-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
z-index: 999;
}
.cart-flyout-backdrop.active {
opacity: 1;
pointer-events: auto;
}
Modal Popups
Use transform instead of changing dimensions:
/* Newsletter popup - no CLS */
.newsletter-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
opacity: 0;
width: 500px;
max-width: 90vw;
background: white;
padding: 30px;
border-radius: 8px;
transition: all 0.3s ease;
pointer-events: none;
z-index: 1001;
}
.newsletter-modal.active {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
pointer-events: auto;
}
Sticky Headers
Reserve space for sticky nav:
/* Main navigation */
.main-nav {
position: sticky;
top: 0;
width: 100%;
height: 80px; /* Fixed height */
background: white;
z-index: 100;
}
/* Prevent collapse on sticky */
.main-nav-spacer {
height: 0; /* No extra space when not sticky */
}
/* When nav becomes sticky */
body.nav-sticky .main-nav {
position: fixed;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
body.nav-sticky .main-nav-spacer {
height: 80px; /* Maintain space */
}
4. Optimize Fonts
Prevent FOUT (Flash of Unstyled Text):
<head>
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/main-font.woff2" as="font" type="font/woff2" crossorigin>
<style>
/* Font display swap minimizes CLS */
@font-face {
font-family: 'Main Font';
src: url('/fonts/main-font.woff2') format('woff2');
font-display: swap; /* or 'optional' for even less CLS */
}
body {
font-family: 'Main Font', Arial, sans-serif;
}
</style>
</head>
CSS Font Loading API:
// Load fonts before showing content
if ('fonts' in document) {
Promise.all([
document.fonts.load('1em Main Font'),
document.fonts.load('bold 1em Main Font')
]).then(() => {
document.documentElement.classList.add('fonts-loaded');
});
}
5. Lazy Load Below-the-Fold Images
Native lazy loading:
<!-- Above the fold - load immediately -->
<img src="hero-image.jpg" width="1200" height="600" alt="Hero">
<!-- Below the fold - lazy load -->
<img src="product-1.jpg"
width="400"
height="400"
loading="lazy"
alt="Product 1">
With placeholder:
<div class="image-wrapper" style="aspect-ratio: 1/1; background: #f0f0f0;">
<img src="product.jpg"
width="400"
height="400"
loading="lazy"
alt="Product">
</div>
Testing and Verification
Pre-Deployment Testing
Test locally:
- Make changes to template/CSS
- Deploy to Volusion staging (if available)
- Test with Chrome DevTools Layout Shift Regions
- Run Lighthouse audit
- Check on different devices
Throttling test:
Chrome DevTools → Network tab → Slow 3G
- Simulates slow connections
- Makes CLS issues more visible
- Shows loading sequence clearly
Post-Deployment Monitoring
PageSpeed Insights:
- Test homepage:
https://yourstore.com - Test category pages
- Test product pages
- Test checkout flow
Target metrics:
- Homepage: CLS < 0.05
- Category pages: CLS < 0.08
- Product pages: CLS < 0.1
- Checkout: CLS < 0.1
Mobile vs Desktop:
- Mobile typically has worse CLS
- Test both separately
- Prioritize mobile optimization
Device-Specific Testing
Real device testing:
Recommended devices:
- iPhone 12/13 (Safari)
- Samsung Galaxy S21 (Chrome)
- iPad (Safari)
- Desktop (Chrome 1920x1080)
Emulation:
- Chrome DevTools → Device Mode
- Test common viewports:
- 375x667 (iPhone SE)
- 390x844 (iPhone 12 Pro)
- 428x926 (iPhone 12 Pro Max)
- 360x800 (Android)
Common Volusion Template Fixes
Category listing template:
<!-- Add to category template -->
<style>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.product-card {
display: flex;
flex-direction: column;
}
.product-image-wrapper {
aspect-ratio: 1/1;
overflow: hidden;
background: #f5f5f5;
}
.product-image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-info {
padding: 15px;
min-height: 100px; /* Reserve for title + price */
}
</style>
Product detail template:
<!-- Main product image -->
<div style="aspect-ratio: 1/1; max-width: 600px;">
<img src="{product_image}"
width="600"
height="600"
alt="{product_name}"
style="width: 100%; height: 100%; object-fit: contain;">
</div>
<!-- Price area -->
<div class="product-price" style="min-height: 40px;">
<span class="price">${product_price}</span>
</div>
When to Contact Volusion Support
Contact Volusion when:
- Template issues beyond your control
- Platform-level performance problems
- Need access to server-side optimizations
- Custom checkout page modifications
- Theme inheritance issues
What to provide:
- CLS scores and PageSpeed reports
- Specific pages affected
- Screenshots of layout shifts
- Browser and device information