---
title: Checkout UI extension performance
description: >-
  Best practices for optimizing the load time and runtime performance of your
  checkout UI extensions.
source_url:
  html: 'https://shopify.dev/docs/apps/build/checkout/extension-performance'
  md: 'https://shopify.dev/docs/apps/build/checkout/extension-performance.md'
api_name: checkout-ui-extensions
---

# Checkout UI extension performance

When a buyer navigates to checkout, Shopify downloads and runs your extension's code asynchronously and independently from the page. Your extension shows a loading state until it becomes interactive. The shorter that window, the better the buyer experience. This page lists best practices for optimizing the performance of UI extensions.

***

## Remove the need for external network calls

Every network call your extension makes at load time adds latency before the buyer sees your UI.

### Use Shopify data APIs

Shopify already provides the data most extensions need. Store your app's own data in [metafields](https://shopify.dev/docs/api/checkout-ui-extensions/latest/target-apis/platform-apis/metafields-api) or [metaobjects](https://shopify.dev/docs/apps/build/metaobjects) instead of fetching it from your server. Read buyer and checkout context through the checkout APIs, such as extension settings and [localization APIs](https://shopify.dev/docs/apps/build/checkout/localized-checkout-ui-extensions). Use the [Storefront API](https://shopify.dev/docs/api/checkout-ui-extensions/latest/target-apis/platform-apis/storefront-api) only as a last resort, for catalog data the checkout APIs don't expose.

The following example reads an app-owned product metafield instead of fetching it from an external server:

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';


export default function extension() {
  render(<Extension />, document.body);
}


function Extension() {
  const metafields = shopify.appMetafields.value;
  const badge = metafields.find(
    (m) => m.target.type === 'product' && m.metafield.key === 'badge',
  );


  if (!badge) {
    return null;
  }


  const {message} = JSON.parse(badge.metafield.value);
  return <s-banner>{message}</s-banner>;
}
```

### If you need external network calls, make them fast

If your extension needs data that you can't store in Shopify metafields or metaobjects, then keep your requests fast and audit your backend response times regularly. Run independent requests in parallel with `Promise.all`, and set a timeout so a slow upstream doesn't keep the extension hidden indefinitely. Treat `shopify.query()` calls the same way: they're network calls too. Fetch the data before first paint, so checkout's skeleton stays in place during the wait and the extension appears once, with no loading flash and minimal layout shift:

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';


export default async function extension() {
  // Only load initial data from the network as a last resort.
  const {data} = await shopify.query(
    `{ products(first: 3) { nodes { id title } } }`,
  );


  render(<Extension products={data.products.nodes} />, document.body);
}


function Extension({products}) {
  return (
    <s-stack direction="block">
      {products.map((product) => (
        <s-text key={product.id}>{product.title}</s-text>
      ))}
    </s-stack>
  );
}
```

***

## Keep your bundle small

Your extension's bundle must download, parse, and execute before it can render. Smaller bundles mean faster load times. Audit your bundle using the [esbuild metafile CLI generation](https://shopify.dev/docs/apps/build/app-extensions#analyzing-bundle-size) to see what's included and where the weight is.

Replace third-party utility libraries with the specific functions you need. For example, swap a date library like `Day.js` or `Moment.js` for the built-in `Intl.DateTimeFormat`. Move large static data to metafields or metaobjects instead of embedding it in your bundle.

If you use error reporting or analytics SDKs, then review your [import configuration](https://shopify.dev/docs/api/checkout-ui-extensions/latest#error-handling) to minimize bundle impact. Use Shopify's localization APIs for translations instead of bundling i18n libraries.

***

## Avoid complex work at module scope

Code at module scope runs before your extension's render callback fires. Anything expensive here delays when the page can start rendering your extension. Don't run network calls, expensive parsing, schema validation, or third-party SDK initialization at module scope.

If you need a value that's expensive to compute, then initialize it lazily so it runs only when your extension actually uses it:

```jsx
let _config;
const getConfig = () =>
  (_config ??= JSON.parse(CONFIG_BLOB));


let _phoneRe;
const getPhoneRe = () =>
  (_phoneRe ??= new RegExp('^\\+?[0-9]{7,15}$'));
```

***

## Read data where you render it

The `shopify` global exposes checkout data as [Preact Signals](https://preactjs.com/guide/v10/signals/). When a component reads `signal.value`, it subscribes to updates. Read signals in the smallest, most specific component possible so that updates don't rerender parent components unnecessarily. If a value doesn't need to update the UI, then read it in an effect or event handler instead.

The following example reads the total cost in a leaf component so that only `Total` rerenders when the value changes:

```jsx
import '@shopify/ui-extensions/preact';
import {render} from 'preact';


export default function extension() {
  render(<Extension />, document.body);
}


function Extension() {
  return (
    <s-stack direction="block">
      <s-heading>Order summary</s-heading>
      <Total />
    </s-stack>
  );
}


function Total() {
  const total = shopify.cost.totalAmount.value;


  return (
    <s-text>
      {total?.amount} {total?.currencyCode}
    </s-text>
  );
}
```

***

## Next steps

[Upgrade to the latest API version\
\
](https://shopify.dev/docs/apps/build/checkout/migrate-to-web-components)

[Migrate to Polaris web components and the latest API version.](https://shopify.dev/docs/apps/build/checkout/migrate-to-web-components)

***
