How to speed up Shopify custom sections
Most slow Shopify pages are slow because of a few heavy sections. These are the fixes that matter most, with Liquid you can copy.
Key takeaways
- Serve images through
image_urlandimage_tagwithwidthsandsizes, so phones never download desktop-sized files. - Lazy-load everything below the fold, but never the hero image: load it eagerly with
fetchpriority: 'high'. - Avoid large JavaScript libraries in a section, and
deferthe scripts you do need. - Reserve space for media with width and height or
aspect-ratioto prevent layout shift. - Keep Liquid loops small with
limit:, and measure before and after with PageSpeed Insights.
A theme can be fast out of the box and still end up slow once custom sections are added. Each section brings its own images, styles and scripts, and a single careless one can undo the rest. The fixes below are the ones that make the biggest difference to a section's weight and to Core Web Vitals, with Liquid that works in any Online Store 2.0 theme.
1. Serve responsive images
Images are usually the heaviest thing in a section. Shopify's CDN can resize and convert them for you, but only if you ask. Use the image_url filter with a width, then image_tag with widths and sizes, so the browser picks the smallest file that looks sharp on the visitor's screen.
{%- if section.settings.image != blank -%}
{{
section.settings.image
| image_url: width: 1200
| image_tag:
loading: 'lazy',
widths: '400, 800, 1200',
sizes: '(min-width: 750px) 50vw, 100vw',
alt: section.settings.image.alt
}}
{%- endif -%}sizes tells the browser how wide the image will be displayed: here, half the viewport on screens 750px and wider, the full width below that. Get it roughly right, because the browser uses it to choose a file before the CSS has loaded. The width passed to image_url should be the largest size you actually need, not the original upload.
2. Lazy-load below the fold, not above it
loading: 'lazy' delays an image until it is close to the viewport, which saves bandwidth for everything further down the page. The exception is the first large image a visitor sees, usually a hero banner. That image is often the page's Largest Contentful Paint, and lazy-loading it makes it later. Load it eagerly and give it a high fetch priority.
{{
section.settings.image
| image_url: width: 2400
| image_tag:
loading: 'eager',
fetchpriority: 'high',
widths: '750, 1100, 1500, 2000, 2400',
sizes: '100vw',
alt: section.settings.image.alt
}}3. Avoid big JavaScript libraries
A slider does not need jQuery plus a full carousel library. CSS scroll snap handles most carousels with a few lines of JavaScript for the arrows, and <details> gives you an accordion with no script at all. Every library you add is downloaded, parsed and executed on every page that includes the section.
- Prefer CSS for layout, transitions and scroll behaviour.
- If you need a library, check whether the theme already loads one before adding another copy.
- Never load the same library twice because two sections each include it.
4. Defer the scripts you do need
A plain <script src> blocks the HTML parser while it downloads and runs. Add defer so the page keeps rendering, and keep the script in the theme's assets folder so it is cached across pages.
<script src="{{ 'testimonial-slider.js' | asset_url }}" defer></script>Write the script so it finds its own section and initialises once, even if the theme editor re-renders the section while the merchant is editing. Scoping the script to the section also means two copies of the section on one page do not interfere.
5. Keep CSS small and scoped
Put a section's styles in a {% stylesheet %} tag, a section-specific asset, or a {% style %} block for values that depend on settings. Prefix every selector with the section's id or a unique class, so the rules apply only where they should and never fight the theme.
{% style %}
#shopify-section-{{ section.id }} .promo {
background: {{ section.settings.background }};
padding-block: {{ section.settings.padding }}px;
}
{% endstyle %}6. Prevent layout shift
Cumulative Layout Shift measures content jumping as the page loads. The usual cause in sections is media without reserved space. image_tag writes width and height attributes for you, which lets the browser reserve the right box. For cropped images, videos and embeds, set an aspect-ratio in CSS.
.promo__media {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.promo__media img,
.promo__media video {
width: 100%;
height: 100%;
object-fit: cover;
}Also avoid injecting banners or badges above existing content after load, and give web fonts a sensible fallback so text does not reflow dramatically when they arrive.
7. Keep Liquid loops small
Liquid runs on Shopify's servers, and heavy loops slow down the time to the first byte of HTML. Use limit: on every loop over a collection, and never nest a loop over all products inside another loop.
{%- assign collection = section.settings.collection -%}
{%- for product in collection.products limit: section.settings.products_to_show -%}
{% render 'product-card', product: product %}
{%- endfor -%}- Let the merchant set the number of products, with a sensible maximum in the schema's range setting.
- Avoid looping over
collectionsorall_productsto find one item; use a product or collection setting instead. - Move repeated markup into a snippet with
render, which keeps the section readable and its variables isolated.
8. Give videos a poster and do not autoplay heavy files
A background video can be several megabytes. Use Shopify's video_tag filter with an image_size for the poster, keep autoplaying videos short and muted, and consider showing only the poster on mobile. For YouTube or Vimeo, show a thumbnail and load the player only when the visitor clicks play.
{%- if section.settings.video != blank -%}
{{
section.settings.video
| video_tag: image_size: '1200x', autoplay: true, loop: true, muted: true, playsinline: true, controls: false
}}
{%- endif -%}9. Measure before and after
Guessing is how performance work goes wrong. Test the same page with and without the section, on mobile settings, and compare.
- PageSpeed Insights shows lab results and, for pages with enough traffic, real-user Core Web Vitals.
- Lighthouse in Chrome DevTools is useful for quick local comparisons; run it a few times, as scores vary.
- Shopify's web performance report (in the admin's Analytics; it replaced the older theme speed score) shows your store's real-visitor Core Web Vitals over time.
- The DevTools Network panel shows exactly which files a section adds and how large they are.
Where to start if a page is already slow
- Find the heaviest sections. Disable sections one at a time in a duplicate of the theme and re-test, or check the Network panel for large images and scripts.
- Fix the hero first: an eagerly loaded, correctly sized image with a high fetch priority often helps Largest Contentful Paint more than anything else.
- Replace oversized images with
image_urlandimage_tagusingwidthsandsizes. - Remove or replace large libraries, and add
deferto the scripts that remain. - Reserve space for every image, video and embed, then re-test for layout shift.
Always test changes on a duplicate theme before publishing, so a mistake never reaches live shoppers.
How Klorr handles this
Every section Klorr writes is checked against a quality bar that includes lazy-loaded media and scoped JavaScript that initialises once, and is touched up when something you named is missing. You can still ask for specifics in an edit, such as "load the hero image eagerly with high priority" or "replace the slider library with CSS scroll snap". See the Shopify Liquid code generator, and how to write better prompts for Shopify sections for getting the details right first time.
Questions, answered
Why is my Shopify section slow?
Usually because of oversized images, a heavy JavaScript library, or a large Liquid loop. Check the Network panel in Chrome DevTools to see what the section downloads, and compare PageSpeed Insights results with and without it.
Should I lazy load all images in Shopify?
No. Lazy-load images below the fold, but load the first large image on the page eagerly, ideally with fetchpriority: 'high'. Lazy-loading the hero image delays the Largest Contentful Paint.
What does the sizes attribute do in Shopify image_tag?
It tells the browser how wide the image will be displayed at different screen sizes. The browser uses it with the widths list to download the smallest file that will look sharp, before the CSS has loaded.
How do I stop layout shift in a Shopify section?
Reserve space for every image and video. image_tag adds width and height attributes automatically, and for cropped media or embeds you can set aspect-ratio in CSS. Avoid inserting content above existing content after the page loads.
How do I test Shopify section performance?
Run PageSpeed Insights or Lighthouse on the same page with and without the section, using mobile settings, and repeat a few times because scores vary. The web performance report in the Shopify admin shows the longer-term trend from real visitors.