---
title: Optimize bundle size
description: >-
  Keep your UI extension bundle small so it downloads, parses, and becomes
  interactive faster.
source_url:
  html: 'https://shopify.dev/docs/apps/build/app-extensions/optimize-bundle-size'
  md: 'https://shopify.dev/docs/apps/build/app-extensions/optimize-bundle-size.md'
---

# Optimize bundle size

UI extensions on API version `2025-10`+ run on Remote DOM, which limits an extension's JavaScript bundle to 64 KB (compressed). Full-page customer account extensions have a 128 KB limit because they can support end-to-end account-management workflows. The device that renders your extension downloads, parses, and runs that bundle first, so a smaller bundle means your extension appears sooner. The limit is enforced per extension at deploy.

This guide applies to [checkout](https://shopify.dev/docs/api/checkout-ui-extensions), [customer account](https://shopify.dev/docs/api/customer-account-ui-extensions), [admin](https://shopify.dev/docs/api/admin-extensions), and [POS](https://shopify.dev/docs/api/pos-ui-extensions) UI extensions.

***

## Measure first

When you run `shopify app build` with Shopify CLI version 3.92.0 or higher, it emits an [esbuild metafile](https://esbuild.github.io/api/#metafile) (`.metafile.json`) in each extension's `dist/` folder. The metafile lists every module in your bundle and how much each one contributes.

To visualize the metafile, upload it to the [esbuild bundle analyzer](https://esbuild.github.io/analyze/) or use a local analysis tool that supports the esbuild metafile format. Start with the modules that contribute the most, and regenerate the metafile after each change to measure the impact.

***

## Replace heavy libraries

Prefer a platform primitive or browser built-in over a dependency. When you do need a library, choose a small, tree-shakeable one and import only what you use.

Common swaps:

| Instead of | Use |
| - | - |
| `react` / `react-dom` | `preact`, the Remote DOM runtime |
| `moment` | `dayjs`, or `date-fns` with per-function imports |
| `lodash` (full import) | the specific functions you need |
| `axios` | the built-in `fetch` |
| `crypto-js` | the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) |
| `Apollo Client` | the platform data APIs, or a plain `fetch` |

***

## Move data out of the bundle

Anything that isn't executable code usually belongs outside the bundle.

* **App data**: store it in [metafields](https://shopify.dev/docs/apps/build/metafields) or [metaobjects](https://shopify.dev/docs/apps/build/metaobjects) and read it at runtime, instead of embedding large object or array literals.
* **Translations**: deliver them through the native localization APIs for your surface: [checkout](https://shopify.dev/docs/apps/build/checkout/localized-checkout-ui-extensions), [customer accounts](https://shopify.dev/docs/apps/build/customer-accounts/localization), [admin](https://shopify.dev/docs/api/admin-extensions/latest/target-apis/core-apis/standard-api), or [POS](https://shopify.dev/docs/apps/build/pos/localization).
* **Images**: consider hosting larger images and referencing them by URL instead of inlining them. A few tiny inline icons are fine, but dozens of them, or any sizable asset, add up quickly.

***

## Remove duplicate dependencies

If two dependencies pull in the same library at different versions, both copies ship. Align the declared versions so it's bundled once.

***

## Ship only the code you use

* Import specific functions, and keep dependencies tree-shakeable (ESM) so the bundler drops what you don't call.
* Prune reachable but unused code: dead imports, side-effectful modules, and non-tree-shakeable dependencies. (Genuinely unreachable modules are already dropped.)

***

## Structure your extensions

When you design and build a new extension, how you split functionality decides what each device downloads.

* **One feature per extension**: Separate capabilities a merchant enables independently, so only the code behind what they turned on gets loaded.
* **Don't combine targets that never render together**: A checkout block bundled with an **Order status** widget, or an admin block for one resource page bundled with an action for another, forces every render to download code that can't run there.
* **A full-page customer account target stands alone**: A full-page target can't be combined with any other target in the same extension, so a full-page extension always has its own bundle and the 128 KB limit to itself.

***

## Request an exception

If your extension can't be optimized under the limit, you can request a bundle-size exception. Exceptions are for extensions that are still over the limit after avoidable bloat is removed, because of genuine feature density or a platform gap with no native alternative yet.

To request one, submit the [bundle-size exception form](https://docs.google.com/forms/d/e/1FAIpQLSeb3yjIfdAardxHZo9DrZW_Qzx88javl_4Zm8Q29N6xCk_rMw/viewform). It asks for your minified bundle and its esbuild metafile, both produced when you build your extension.

***

## Next steps

The bundle limit applies from API version `2025-10`. If your extension is on an earlier version, then upgrade it to Polaris web components and Remote DOM:

| Surface | Upgrade guide |
| - | - |
| Checkout | [Upgrade to the latest API and web components](https://shopify.dev/docs/apps/build/checkout/migrate-to-web-components) |
| Customer accounts | [Upgrade to the latest API and web components](https://shopify.dev/docs/apps/build/customer-accounts/migrate-to-web-components) |
| Admin | [Upgrading to 2025-10](https://shopify.dev/docs/apps/build/admin/upgrading-to-2025-10) |
| POS | [Upgrading to 2025-10](https://shopify.dev/docs/apps/build/pos/upgrading-to-2025-10) |

***
