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.
Anchor to Remove the need for external network callsRemove the need for external network calls
Every network call your extension makes at load time adds latency before the buyer sees your UI.
Anchor to Use Shopify data APIsUse Shopify data APIs
Shopify already provides the data most extensions need. Store your app's own data in metafields or metaobjects instead of fetching it from your server. Read buyer and checkout context through the checkout APIs, such as extension settings and localization APIs. Use the 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:
Anchor to If you need external network calls, make them fastIf 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:
Anchor to Keep your bundle smallKeep 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 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 to minimize bundle impact. Use Shopify's localization APIs for translations instead of bundling i18n libraries.
Anchor to Avoid complex work at module scopeAvoid 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:
Anchor to Read data where you render itRead data where you render it
The shopify global exposes checkout data as Preact 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: