---
title: 'Storefront: Lazy Loading and Performance Optimization | Shopware Community Hub'
description: >-
  Learn how to improve storefront performance with lazy loading, deferred
  JavaScript execution, and practical measurement techniques.
canonical_url: >-
  https://hub.shopware.com/learn/unit/storefront-lazy-loading-and-performance-optimization
---

# Storefront: Lazy Loading and Performance Optimization

<LearningObjectives>

- Understand the concept of lazy loading and how it improves storefront performance.
- Apply `loading='lazy'` to images in Twig templates using `sw_thumbnails`.
- Understand how delayed storefront JavaScript plugins using the Intersection Observer API can reduce initial page load work.
- Use Chrome DevTools and Lighthouse to measure performance and evaluate optimizations.
- Keep accessibility guardrails in mind when applying performance optimizations.

</LearningObjectives>

# Storefront: Lazy Loading and Performance Optimization

Storefront performance directly affects user experience and perceived speed. Small optimizations can significantly improve how fast a shop feels to customers.

In this learning unit, you will build a solid understanding of common storefront performance techniques used in Shopware projects.

The goal is to help you understand when and why certain optimizations are useful so that you can apply them and evaluate their impact in real projects.

You will learn how to:

- Load images later (lazy loading) where appropriate.
- Reduce initial JavaScript work by delaying plugin initialization.
- Measure impact using DevTools and Lighthouse.
- Keep basic accessibility guardrails in mind while optimizing performance.

## Lazy Loading and Image Optimization

Images are often among the largest assets in a storefront. Optimizing how and when they are loaded can significantly improve page performance and reduce unnecessary network usage.

### Lazy Loading

Lazy loading is a technique that delays loading assets until they are likely to become visible to the customer.

In the storefront, this usually applies to images that are outside the initial viewport.

The goal is to load critical resources first and delay non-critical assets until they are actually needed. This allows the browser to focus on rendering the most important content first.

Keep in mind that native lazy loading is a **browser feature**. The browser decides *when exactly* an image starts loading based on heuristics (it’s not a fixed distance rule).

#### Why it Improves Performance

Lazy loading can improve storefront performance for several reasons:

- **Less network traffic during initial load**:
   The browser can prioritize the resources required for the first viewport.

- **Faster rendering for the first view**:
   When fewer images compete for network and CPU resources, the browser can render the visible part of the page faster.

- **Better perceived performance**:
   Customers often perceive the page as faster because they can interact with the visible content sooner, especially on slower mobile connections.

#### When Not to Use Lazy Loading

Lazy loading works best for images that appear further down the page.

Good practices include:

- **Load the above-the-fold images immediately**:
   Images that appear in the first viewport should usually load as quickly as possible. Lazy loading them may delay the most important visual content.
- **Load key UX elements immediately**:
    Important images, e.g., primary product image, should appear instantly to maintain a good user experience.
- **Reserve layout space for images**:
   Ensuring that the layout reserves space for images prevents layout shifts when images load.

<Callout title="Note" type="info">

In Shopware storefronts, CMS layouts and template composition can be dynamic. Because of this, lazy loading decisions often depend on the specific layout and use case.

Instead of applying lazy loading everywhere, developers should evaluate which image can safely load later.

</Callout>

### Apply `loading="lazy"` in Shopware Twig

Shopware storefront templates commonly render images using the `sw_thumbnails` Twig helper, as you learned in the previous course.

This helper supports the `loading="lazy"` attribute, which enables native lazy loading for images.

In addition, modern browsers support the `fetchpriority` attribute. It is a hint that helps the browser decide whether a resource should be fetched with a higher or lower priority. Possible values are `high`, `low`, and `auto` (default).

Typical places where lazy loading can be useful include:

- Product listing cards
- CMS image elements
- Line item images

**Minimal example**

```twig
{% sw_thumbnails 'product-cover' with {
    media: product.cover.media,
    attributes: {
        'loading': 'lazy', {# <-- This is the attribute #}
        'fetchpriority': 'low', {# <-- Optional: a hint for the browser #}
        'alt': product.translated.name,
        'title': product.translated.name
    }
} %}
```

**More complex example**

```twig
{% sw_thumbnails 'navigation-flyout-teaser-image-thumbnails' with {
    media: category.media,
    sizes: {
        default: '310px'
    },
    attributes: {
        'class': 'navigation-flyout-teaser-image img-fluid',
        'alt': (category.media.translated.alt ?: ''),
        'title': (category.media.translated.title ?: ''),
        'data-object-fit': 'cover',
        'loading': 'lazy', {# <-- This is the attribute #}
        'fetchpriority': 'low' {# <-- Optional: deprioritize non-critical images #}
    }
} %}
```

**Example: Prioritize the main (LCP) image**

If you have one image that is clearly the most important above-the-fold image (often the Largest Contenful Paint (LCP) image), you can load it immediately and hint a higher priority:

```twig
{% sw_thumbnails 'pdp-cover' with {
    media: product.cover.media,
    attributes: {
        'loading': 'eager',
        'fetchpriority': 'high',
        'alt': product.translated.name
    }
} %}
```

<Callout title="Tip" type="info">

If you render images inside loops (e.g., in product listings), keep the Twig logic simple. Lazy loading helps reduce network work, but complex Twig logic can still slow down server-side rendering.

</Callout>

#### Decide: Immediately Loading vs. Lazy Loading

A simple rule set works well for most storefront scenarios:

**Load immediately:**

- Images that are visible when the page first loads.
- Product images and important brand visuals.
- The first product gallery image on a PDP.

**Lazy loading:**

- Images further down the page.
- Listing images in long product lists.
- CMS blocks below the fold
- Images in sections that are typically below the fold (e.g., cross-selling further down the PDP).

A helpful rule of thumb is: If an image is part of what the customer sees immediately when the page loads, it should usually load immediately and **not** be lazy-loaded.

#### Common Pitfalls

When applying lazy loading, avoid the following mistakes:

- **Lazy loading the main image**: The most important visual content should never be delayed.
- **Not reserving space for images**: Container elements should define dimensions so the layout remains stable and layout shifts are avoided.
- **Missing alt text**: Performance optimizations should never reduce accessibility. Always provide a meaningful `alt` attribute.

### Image Optimization

Lazy loading is only one part of image performance. Image formats, sizes, and layout stability also play an important role.

#### Choose Good Formats and Compression

Images should be optimized before they reach the storefront.

Good practices include:

- Compress images before uploading them (smaller files load faster).
- Prefer modern formats such as **WebP** and AVIF when it's supported by your project.
- Avoid uploading images that are significantly larger than needed.

Smaller image files reduce network usage and improve loading times.

#### Responsive Images (`srcset` / `sizes`)

Modern web browsers support responsive images, which allow the browser to choose the most appropriate image size for the current device.

Shopware’s thumbnail system generates responsive image candidates when using `sw_thumbnails`. The key idea is simple:

- `srcset` provides multiple image sizes.
- `sizes` tells the browser how large the image will appear on the page, so it can pick the best candidate from `srcset`.

Based on this information, the browser selects the best image size for the current viewport.

**Example:**

```twig
{% sw_thumbnails 'listing-cover' with {
    media: product.cover.media,
    sizes: {
        xs: '140px',
        sm: '180px',
        md: '220px',
        lg: '260px',
        xl: '300px'
    },
    attributes: {
        'loading': 'lazy',
        'alt': product.translated.name
    }
} %}
```

#### Reduce Layout Shifts

Another important performance factor is the Cumulative Layout Shift (CLS). Layout shifts happen when the page layout changes while content is loading.

To maintain a stable layout:

- Ensure that image containers reserve space before the image loads.
- Use stable layout techniques such as fixed container dimensions or CSS `aspect-ratio`.
- Keep the layout consistent so content does not move unexpectedly.

These adjustments help maintain layout stability and improve the overall user experience.

## Deferring Storefront JavaScript Work

Storefront JavaScript can be a hidden performance cost. Even when HTML, CSS, and images are well optimized, a page may still feel slow if too much JavaScript executes during the initial load.

When many plugins initialize at the same time, they compete for the browser's main thread, which can delay rendering, block interaction, and reduce perceived performance.

A useful performance strategy is to reduce the amount of JavaScript work during the initial page load by delaying tasks until they are actually needed.

<Callout title="Note" type="info">

This learning unit focuses on the performance idea: **Less JavaScript work on the initial load** can make the storefront faster.

</Callout>

### Why Delay Storefront Plugin Initialization?

Delaying JavaScript work can improve storefront performance in several ways.

- **Faster initial rendering:**
   When fewer plugins run immediately, the browser can render the first visible content faster.
- **Less main thread blocking:**
   Heavy JavaScript execution can create long tasks that block user interaction. Delaying work reduces these long tasks.
- **Better scalability for storefront features:**
   You can keep advanced functionality without executing all of it on every page load.
- **Binding storefront JavaScript plugins to elements:**
   You can bind storefront JavaScript plugins to the related element, which then only loads when the element is found.

In practice, many storefront components do not run immediately when the page loads. Instead, they only need to execute when the customer interacts with them or scrolls to them.

### Two Practical Strategies in Shopware

There are two common strategies to reduce JavaScript work during the initial page load.

#### Strategy 1: Load the Plugin Only When It Is Necessary

Some storefront JavaScript plugins are only required on specific pages or components. In these situations, the plugin can be registered as an **async plugin** using dynamic imports.

This allows the browser to download the plugin code only when the corresponding DOM element exists. Pages that do not use the plugin therefore do not download the code, which keeps the initial page load lighter and faster.

#### Strategy 2: Run Heavy Logic Only When the Element Becomes Visible (Intersection Observer)

Sometimes the plugin must exist on the page, but its heavy logic does not need to run immediately. For example:

- Sliders that appear further down the page.
- Recommendation blocks
- Interactive widgets below the fold.

In these cases, you can delay the expensive work until the element becomes visible. The browser API **Intersection Observer** can help with this. It allows you to detect when an element enters the viewport.

**Example: Delaying plugin logic with Intersection Observer**

A simplified example illustrates this idea:

- Your template renders an element like `<div data-my-plugin></div>`.
- Shopware initializes the plugin on a page load.
- The plugin waits until the element is visible, then loads/executes the heavy code.

```js
const { PluginBaseClass } = window;

export default class MyPlugin extends PluginBaseClass {
    init() {
        const observer = new IntersectionObserver((entries) => {
            if (!entries.some(e => e.isIntersecting)) {
                return;
            }

            observer.disconnect();

            // Heavy work starts only now (you can also dynamic import here)
            this.runHeavyLogic();
        });

        observer.observe(this.el);
    }

    runHeavyLogic() {
        // e.g. build a slider, fetch data, attach many listeners
    }
}
```

In this example, the plugin initializes normally. You define an `IntersectionObserver` object, where you check if the element is visible. If it is, the plugin runs its heavy logic.

Here is what happens step by step:

- Plugin is created and `init()` is called: Shopware initializes the plugin as usual.
- Create an observer: `new IntersectionObserver(...)` registers a callback function.
- Wait until visible: As long as `entries.some(e => e.isIntersecting)` is `false`, the callback returns and does nothing.
- Stop observing: `observer.disconnect()` ensures the callback will not run again on every scroll event.
- Run the heavy work: `this.runHeavyLogic()` is only called once the element is visible.

<Callout title="Tip" type="info">

Use this strategy for components that are below the fold (sliders, recommendation blocks, widgets). It improves the first load because the browser does less JavaScript work up front.

</Callout>

<Callout title="Layout Stability for Delayed Components" type="info">

If delayed JavaScript builds a visual component, such as a slider, reserve the required layout space with CSS before the plugin runs. Otherwise the page can jump when the slider initializes, which hurts the Cumulative Layout Shift (CLS).

For example, define a stable wrapper height, aspect ratio, or skeleton layout for below-the-fold sliders and recommendation blocks. Delaying JavaScript should reduce initial work, not create layout movement later.

</Callout>

### Intersection Observer vs. Async Plugins

Both techniques help performance, but they solve different problems:

- **Async plugin registration** (dynamic import) controls **when plugin code is downloaded**.
   The browser downloads the plugin only if the matching DOM selector exists in the page. This keeps the initial JavaScript bundle smaller.
- **Intersection Observer** controls **when plugin logic runs**. The plugin may already exist on the page, but heavy work starts only when the element becomes visible (below the fold).

**Typical usage:**

- Use **async plugins** when the feature is **not needed on every page** (e.g., only on PDP or only in one CMS element).
- Use **Intersection Observer** when the element **is on the page**, but users might not scroll to it (sliders, recommendation blocks, widgets further down).

In many Shopware projects both techniques are combined. The plugin is loaded asynchronously, and expensive logic starts only when the component becomes visible.

### Avoid Many Small Requests

Delayed JavaScript can also trigger server requests, for example to load wishlist state, recommendations, or other product-related information.

Be careful with product listings: Do not send one request per product card if the information can be loaded in one grouped request. A category page with 24 products should not create 24 separate wishlist-state requests during initialization.

The practical mental model is: **Batch related state where possible**. Load the information for the visible product set together, then update the matching elements in the DOM. This keeps the browser and the server from doing unnecessary repeated work.

## Measuring Performance Improvements

Performance optimizations should always be verified with measurements.

Without measurements, it is easy to assume that something is faster while it may accidentally make it worse or introduce new problems. For example, aggressively lazy loading images might delay important product images on a PDP, which can worsen the Largest Contentful Paint (LCP) and make the page feel slower to customers.

### Measuring With Chrome DevTools

Chrome DevTools allows quick and practical checks directly in the browser.

**Network tab (images and JavaScript downloads):**

The Network tab helps you inspect which resources are downloaded when the page loads. Typical things to verify on page reloads:

- Images below the fold should usually not be downloaded immediately after lazy loading is applied.
- When using async plugins, the corresponding JavaScript files should only be downloaded on pages where the plugin is actually used.

**Performance tab (runtime work):**

The performance tab helps analyze how much work the browser performs while loading the page.

After applying optimizations such as async plugins or delayed initialization, you should typically observe:

- Less JavaScript work during the early phase on the page load.
- Fewer long tasks blocking the main thread.

### Measuring With Lighthouse

Lighthouse is useful for before-after comparisons because it summarizes common performance issues in a single report. A typical workflow is:

- Run a **baseline** report first.
- Apply one optimization (e.g., lazy loading in product listings).
- Run Lighthouse again and compare the results.

After starting a Lighthouse report, the tool analyzes the page and generates a summary of performance metrics and recommendations.

**Example:**

![Chrome Browser: Open Lighthouse Tab](assets/images/browser-open-lighthouse-tab.jpg)

Lighthouse provides several configuration options. You can choose the device mode (mobile or desktop) and select which categories should be analyzed.

In the following example, the Desktop mode is selected and all categories (performance, accessibility, best practices, SEO) are enabled.

![Lighthouse: Select Parameter](assets/images/browser-lighthouse-tab-parameter-selection.jpg)

During analysis, it will look like this:

![Lighthouse: Analyzing](assets/images/browser-lighthouse-analyzing.jpg)

Once the analysis is complete, you will see a report like this:

![Lighthouse Report: Performance Part One](assets/images/browser-lighthouse-analyzed-performance_one.jpg)

![Lighthouse Report: Performance Part Two](assets/images/browser-lighthouse-analyzed-performance-two.jpg)

![Lighthouse Report: Performance Part Three](assets/images/browser-lighthouse-analyzed-performance_three.jpg)

![Lighthouse Report: Accessibility](assets/images/browser-lighthouse-analyzed-accessibility.jpg)

![Lighthouse Report: Best Practice](assets/images/browser-lighthouse-analyzed_best_practice.jpg)

![Lighthouse Report: SEO](assets/images/browser-lighthouse-analyzed-SEO.jpg)

Each category shows a summary of the results. You can expand the sections to view more details.

If you want to dive deeper into Lighthouse, feel free to read the [official documentation](https://developer.chrome.com/docs/lighthouse/overview).

<Callout title="Important" type="info">

Lighthouse results can vary between runs. To compare fairly, keep conditions similar (same page, same device mode, similar network conditions, minimal background load).

</Callout>

### Suggested Workflow

A simple workflow for performance testing:

1. Pick one page (e.g., a category listing).
2. Measure once (DevTools and Lighthouse) and note the baseline.
3. Apply one optimization.
4. Measure again and compare the results.
5. Keep the change only if it improves performance without breaking UX or accessibility.

## Accessibility Guardrails While Optimizing

Performance optimizations must not make the storefront harder to use. Keep the accessibility check close to the optimization you changed:

- **Images**: Keep meaningful `alt` text. Lazy loading should not remove or weaken image semantics.
- **Delayed JavaScript**: Do not delay critical controls that customers need immediately.
- **Hidden UI**: If an element is visually hidden, make sure keyboard focus cannot reach it accidentally.
- **Dynamic UI**: If JavaScript loads or replaces interactive content, check keyboard behavior and focus flow.

Use native HTML first (`button`, `a`, `input`) and add ARIA only when it describes a state or label that native HTML cannot provide on its own.

## Practical Checklist

Use this short checklist after each change. It helps you keep performance improvements and accessibility together.

- **Images**: Lazy load only below the fold; keep alt text; avoid layout shifts.
- **JavaScript**: Delay heavy work but keep interactions accessible (keyboard and focus).
- **Verify**: Test with keyboard only (Tab/Enter/Esc) and re-run Lighthouse accessibility checks.

## Summary

In this learning unit, you learned:

- What lazy loading is and when it should (or should not) be used in the storefront.
- How to apply `loading="lazy"` in Shopware Twig templates using `sw_thumbnails`.
- How to reduce JavaScript work during the initial page load with async plugins and Intersection Observer.
- How to measure your improvements using Chrome DevTools and Lighthouse.
- How to keep accessibility in mind while optimizing performance.

With this knowledge, you can apply practical performance improvements to your storefront and verify that they actually help.
