Fix LCP in Shopify
Largest Contentful Paint (LCP) is the time it takes for the main visual element of your page -- usually the hero banner or a large product image -- to appear on screen. Most Shopify stores score poorly on LCP because hero images are discovered late by the browser, third-party app scripts block rendering, and legacy themes were never designed with Core Web Vitals in mind. This guide targets the five changes that move most Shopify stores from a poor or needs improvement LCP score into Google's Good range (under 2.5 seconds), even for visitors on mobile connections.
Expected results
Before
4.8s
LCP (Poor) -- unoptimized hero, app scripts blocking render, no preload
After
2.0s
LCP (Good) -- preloaded hero, app audit complete, Dawn theme
Step-by-step fix
Use Shopify's native image_tag with explicit dimensions
Shopify's image_tag Liquid filter is the correct way to render images in your theme. Unlike a raw <img> tag, image_tag automatically generates a srcset using Shopify's CDN, outputs the correct image URL format, and supports all performance attributes as named parameters. The single most important setting for your hero image is loading: 'eager' combined with explicit pixel dimensions. When the browser sees a known width and height, it reserves space for the image before it loads, preventing Cumulative Layout Shift (CLS) and allowing the browser to begin layout immediately. Never use loading: 'lazy' on your hero or any above-the-fold image -- lazy loading tells the browser to defer the fetch, which directly increases LCP.
{%- comment -%}
Bad: raw img tag, no dimensions, no priority hint
{%- endcomment -%}
<img src="{{ section.settings.hero_image | img_url: '1200x' }}" alt="Hero">
{%- comment -%}
Good: image_tag with explicit dimensions and eager loading
{%- endcomment -%}
{{ section.settings.hero_image | image_tag:
width: 1200,
height: 600,
loading: 'eager',
fetchpriority: 'high',
class: 'hero__image',
alt: section.settings.hero_image.alt
}}
{%- comment -%}
For responsive hero images, pass sizes parameter:
{%- endcomment -%}
{{ section.settings.hero_image | image_tag:
widths: '375, 750, 1100, 1500',
sizes: '100vw',
loading: 'eager',
fetchpriority: 'high'
}}
Minimize app impact on storefront
Each Shopify app that injects scripts or styles into your storefront adds weight that the browser must download and parse before it can paint. Review apps that show star ratings on every product page are a common culprit -- they often add 30 to 80KB of JavaScript loaded synchronously. Chat widgets, upsell pop-ups, currency converters, and loyalty program scripts all contribute. Open Shopify Admin and navigate to Sales Channels > Online Store > Themes, then click Customize and open the App Embeds tab. Disable any app embed you do not actively use. For a deeper audit, install the Shopify Theme Inspector Chrome extension. It shows a waterfall of every resource your theme loads and which apps contributed them. Remove unused app code from layout/theme.liquid if it was manually added rather than injected via the App Embeds system.
{%- comment -%}
Audit: search theme.liquid for inline app scripts
These are often left behind when apps are uninstalled
{%- endcomment -%}
{%- comment -%} Remove or defer manually-injected app scripts: {%- endcomment -%}
{%- comment -%}
Bad (blocks rendering):
<script src="https://cdn.app-name.com/widget.js"></script>
Good (defer non-critical apps):
<script src="https://cdn.app-name.com/widget.js" defer></script>
Best (load after LCP is painted):
{%- endcomment -%}
<script>
window.addEventListener('load', function() {
var s = document.createElement('script');
s.src = 'https://cdn.app-name.com/widget.js';
document.head.appendChild(s);
});
</script>
{%- comment -%}
Use content_for_header for App Embeds (Shopify manages these):
{%- endcomment -%}
{{ content_for_header }}
Preload the hero image in theme.liquid
Even with loading: 'eager', the browser cannot start fetching your hero image until it has parsed the HTML far enough to encounter the <img> tag. On a Shopify store, that image is usually inside a section file that is rendered partway through the page. A rel="preload" link tag in the <head> of layout/theme.liquid tells the browser to start fetching the image immediately from the first byte of the HTML response -- before it even reaches the section markup. Combine this with the image_tag optimization from step 1 for maximum effect. Capture your hero image URL using Liquid's img_url filter and write it directly into the preload link. Because the hero image is section-specific, gate the preload with a conditional that checks the current template.
<!-- layout/theme.liquid: add inside <head> -->
{%- if template == 'index' -%}
{%- assign hero_section = sections['hero-banner'] -%}
{%- if hero_section.settings.hero_image -%}
<link
rel="preload"
as="image"
href="{{ hero_section.settings.hero_image | img_url: '1200x' }}"
imagesrcset="{{ hero_section.settings.hero_image | img_url: '400x' }} 400w,
{{ hero_section.settings.hero_image | img_url: '800x' }} 800w,
{{ hero_section.settings.hero_image | img_url: '1200x' }} 1200w"
imagesizes="100vw"
fetchpriority="high"
>
{%- endif -%}
{%- endif -%}
{%- comment -%}
Also preconnect to Shopify's CDN for faster asset delivery:
{%- endcomment -%}
<link rel="preconnect" href="https://cdn.shopify.com">
Use section-based lazy loading
Shopify 2.0's Sections architecture lets you break your page into modular, independently-rendered blocks. Each section is a self-contained Liquid file with its own schema, CSS, and JavaScript. Sections below the fold -- product collections, testimonials, blog previews, newsletter sign-up blocks -- should not load their assets until the visitor is about to scroll to them. Use the {% render %} tag (which replaced the deprecated {% include %} tag) to include section files. Within each below-fold section, add loading="lazy" to any images and use a lightweight Intersection Observer in section JavaScript to defer heavier operations. This prevents below-fold content from competing with the hero image for network bandwidth during the critical first-load window.
{%- comment -%}
Below-fold section: use lazy loading for all images
{%- endcomment -%}
<div class="featured-collection" data-section-id="{{ section.id }}">
{%- for product in section.settings.collection.products limit: 8 -%}
<div class="product-card">
{{ product.featured_image | image_tag:
width: 600,
height: 600,
loading: 'lazy',
class: 'product-card__image',
alt: product.featured_image.alt | default: product.title
}}
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{%- endfor -%}
</div>
{%- comment -%}
In templates/index.json -- sections render in order,
hero first, collection second (below fold):
{%- endcomment -%}
{
"sections": {
"hero": { "type": "hero-banner" },
"collection": { "type": "featured-collection" }
},
"order": ["hero", "collection"]
}
Choose a performance-optimized theme (Dawn)
If your store is running on a legacy theme such as Debut, Brooklyn, or Narrative, the theme itself is a significant source of LCP and overall performance problems. These themes were built before Core Web Vitals became a ranking signal and before Shopify 2.0's architecture existed. Shopify's Dawn theme, released alongside Online Store 2.0, is purpose-built for performance. It uses image_tag throughout, removes jQuery in favor of vanilla JavaScript, uses CSS custom properties for theming instead of injected inline styles, and defers non-critical JavaScript. Dawn also ships with a theme.json settings file that lets you control font preloading, color scheme variables, and section defaults without touching Liquid. When evaluating themes, check PageSpeed Insights scores for the live demo before purchasing. Any commercial theme claiming to be performance-focused should score 80 or above on mobile without customization.
// Dawn uses Shopify's font library for system-level font preloading.
// In the Theme Customizer under Typography, select fonts from
// Shopify's native font picker. Dawn auto-generates preload links.
// config/settings_data.json (generated by Theme Customizer):
{
"current": {
"type_header_font": "helvetica_neue_n7",
"type_body_font": "helvetica_neue_n4",
"heading_scale": 100,
"body_scale": 100
}
}
// Dawn theme.liquid automatically outputs:
// <link rel="preload" as="font" ...> for selected Shopify fonts
// and uses font-display:swap for all custom font faces.
// To audit Dawn performance settings in Customize:
// Theme Customizer > Theme Settings > Typography
// -- Select system fonts (fastest) or Shopify-hosted fonts
// -- Avoid Google Fonts loaded via external stylesheet
// Theme Customizer > Theme Settings > Performance
// -- Enable lazy loading (applies to all below-fold images)
Quick checklist
-
Hero image uses
image_tagwith explicitwidth,height, andloading: 'eager' - App Embeds panel reviewed -- unused app embeds disabled
-
rel="preload"link for hero image added inlayout/theme.liquidhead -
Below-fold sections use
{% render %}tag andloading="lazy"on images - Theme is Dawn or another 2.0-compatible theme with verified PageSpeed score above 80 on mobile
-
Fonts selected from Shopify's native font library or configured with
font-display:swap
Frequently asked questions
Google's threshold for a good LCP is under 2.5 seconds. For Shopify stores, a realistic target on a fast connection is 1.8 to 2.2 seconds. Stores on mobile with slow connections often score 4 to 6 seconds before optimization. Focusing on hero image preloading, reducing third-party app scripts, and adopting the Dawn theme typically brings most stores under the 2.5 second threshold. Use Google's PageSpeed Insights to measure your actual field data LCP, which reflects what real visitors experience.
Yes, significantly. Every app that injects JavaScript or CSS into your storefront adds resources the browser must download and parse before it can paint. Review apps (star ratings), live chat widgets, pop-up tools, and currency converters are common offenders. Use the Shopify Theme Inspector Chrome extension to see exactly which apps are contributing weight. Removing or deferring a single heavyweight app often reduces LCP by 0.5 to 1.5 seconds.
Dawn is substantially faster than Debut for LCP. Dawn removes jQuery entirely in favor of vanilla JavaScript, uses Shopify's image_tag filter with dimensions throughout, defers non-critical scripts, and uses CSS custom properties for theming rather than injected inline styles. Debut was built before Core Web Vitals became a ranking factor and before Online Store 2.0 architecture was available. For most stores migrating from Debut to Dawn, LCP improvements of 1 to 2 seconds are typical without any additional optimization work.
Open Chrome DevTools, go to the Performance tab, and run a page recording with CPU throttling set to 4x and network throttling to Slow 4G. Look for the LCP marker in the trace timeline. Alternatively, run a Lighthouse audit -- the report identifies the exact LCP element and its URL. In most Shopify stores the LCP element is the hero banner image, a large above-fold product image, or occasionally a large heading text block on text-heavy pages.
Shopify's built-in CDN, powered by Fastly, serves all uploaded assets from edge nodes close to your visitors, which reduces image time-to-first-byte. However, CDN delivery alone does not solve LCP problems caused by late image discovery, missing preload hints, or JavaScript blocking. The CDN is a good foundation, but the preloading and app-reduction steps in this guide are still required to achieve a strong LCP score.
Google rates LCP as 'good' when it is under 2.5 seconds at the 75th percentile. For Shopify applications specifically, aim for under 2.0 seconds. Measure with field data from Chrome User Experience Report (CrUX) through PageSpeed Insights, as lab tests may not reflect real-user experience with third-party scripts and varying network conditions.
Related resources
Complete LCP Guide
Deep dive into LCP measurement, thresholds, element types, and optimization strategies for any stack.
FixFix LCP in WordPress
WordPress-specific LCP fixes including image optimization plugins, caching, and theme performance settings.
FixFix LCP in Webflow
Webflow LCP fixes covering image settings, custom code deferral, and interaction optimization.