Shipping a Sub-10MB React Native App Clip

When iOS 14 introduced App Clips, Shopify saw an immediate opportunity for its Shop app. The core problem was a funnel drop-off: users who completed a purchase often failed to install the full app, despite its strong ratings. An App Clip—a lightweight app launched on demand without an App Store visit—could bridge that gap. The challenge: build one in React Native and keep it under Apple's hard 10MB limit.

App Clips are invoked rather than installed, triggered by QR codes, NFC tags, Messages links, or Smart App Banners. iOS downloads the binary in the background and launches it instantly with the invocation URL, enabling context-aware experiences. At the time, this was an untested technical frontier—no one had built an App Clip at this scale with React Native.

Validating the Idea with a Fail-Fast Plan

The engineering team, sharing Shopify's commitment to React Native, structured the project to fail quickly if the approach proved impossible. The plan had three checkpoints:

  1. Build a "Hello World" App Clip in React Native and measure its size.
  2. Build a scrappy, non-functional version with all estimated code and dependencies to test the size ceiling.
  3. Iterate on a clean, working implementation.

Product validation was equally important. App Clips were unfamiliar to users, so the goal was to ship a test version fast. Success would justify further investment.

The first test exposed a major issue: a barebone App Clip with only <View /> and <Text /> weighed in at a massive 28MB. The culprit was that the App Clip bundled all native dependencies from the Shop app's Podfile, not just the React Native ones. The solution was explicit dependency management—cherry-picking pods for the App Clip target by examining what React Native truly needed. This brought the size down to 4.3MB, proving the concept was at least theoretically possible.

Building Up from a 4.3MB Baseline

With a viable baseline, the team added estimated features to test real-world size constraints. Unlike auto-linking (which scans all installed packages), every dependency had to be manually added to the App Clip's Podfile target. The feature set included GraphQL network calls, error reporting, and push notifications—all added without fully working implementations, purely to gauge size impact.

The result was 6.5MB—tight but workable. With roughly 3.5MB of headroom, the project moved forward, but the margin left little room for error.

Designing Within Technical Limits

Given the strict size constraints, the team flipped the typical process: technology drove design. If a design element pushed close to the 10MB limit, it went back to the drawing board. The App Clip aimed to show users their recent order details and offer push notifications for shipping updates—a simplified version of the Shop app's order view.

Sharing Code Without Sharing the Size

The ideal outcome was code sharing between the full Shop app and the App Clip. But sharing the entire <OrderView /> proved problematic. Not only would it complicate the main app's order screen, but any future change there—say, adding a heavy dependency—could silently push the App Clip past its limit.

The team considered sharing only subcomponents while keeping <OrderView /> separate. <ProductRow />, which displays product title, price, and image, was the first candidate. But importing it caused a crash: the component relied on react-native-fast-image, a native Swift library for performant image lists, which hadn't been explicitly added to the Podfile. Adding it fixed the crash but hinted at a deeper problem: with only 3.5MB of headroom, adding a single native dependency could be the breaking point.

A review of <OrderView />'s subcomponents confirmed the worst—most had native dependencies, and one alone would add a staggering 2.5MB. Sharing components with the main app was off the table.

A Pragmatic Decision

Facing the crossroad of duplication versus shared code, the team leaned on three principles:

  • Speed of validation: The App Clip was an experiment, so it needed to launch quickly to prove user value.
  • Isolation: A small team wouldn't burden other Shop developers with responsibility for the App Clip's size during routine changes.
  • Disposability: Code should be simple to delete if the experiment didn't pay off.

This led to a decisive, counterintuitive conclusion: the similarities between the Shop app's order view and the App Clip would be treated as coincidental. Duplicating the necessary UI was the right trade-off—it freed the team to move fast and avoid constraints that would slow iteration.

Keeping the Clip Under the Size Limit

Development of the App Clip itself borrowed most of its workflow from standard React Native builds. The principal difference was the constant need to monitor bundle size. Since each size check took roughly 25 minutes locally, the team ran it only when adding new dependencies, with occasional ad-hoc checks between releases.

Most App Clip components were written from scratch. The exceptions were shared components and functions from the Shop app's shared/ directory, including the <Box /> and <Text /> primitives built on the Restyle library, plus hooks for setup tasks like push notifications and user creation. Sharing code across the app and the clip carries a risk: a developer adding a native dependency to a shared component—perhaps unknowingly—would inflate the App Clip as well.

The team considered those core shared components stable enough to reuse, but added a CI script to catch accidental regressions. The script performs three checks:

  1. Parses the Podfile and lists all native dependencies.
  2. Walks through every import made by the App Clip and lists those with native dependencies.
  3. Compares the lists; a mismatch fails the CI job with instructions on how to proceed.

This guard caught several issues over time, from both internal shared code and third-party libraries (animation, async storage, device information). When a dependency was deemed non-critical, the team removed it entirely—consistent with the project’s "technology drives design" principle. The animation library, for example, was dropped rather than accommodated.

Handling Invocations and App Installs

When a user scans a QR code, the full iOS app takes precedence over the App Clip if it is already installed. That means the team needed invocation support inside the main app too, so existing users could scan a code and have the order imported automatically.

React Native's Linking module provided the mechanism to fetch the invocation URL within the app and create the order, unifying the QR code experience for both App Clip and app users.

Onboarding Without Friction

The final piece was a seamless transition from the App Clip to the full app. Users upgrading from the clip deserved a simpler onboarding flow—ideally with their order already waiting. Apple's shared data containers, accessible by both the App Clip and the main app, made this possible. User data collected for the order in the clip can be read by the app, enabling a near-instant setup for new users.

Testing Against Production Realities

Testing throughout the project proved challenging. Apple's Xcode tools can mock an invocation by hard-coding the URL, but nothing short of a live release can exercise the full path: scanning the QR code, launching the clip, and downloading the app. Neither local development nor TestFlight could replicate that end-to-end flow.

To verify the production experience, the team released a minimal version of the App Clip very early, gated by beta flags so only internal users could invoke it. This first release had no functionality beyond confirming the invocation URL was received and that the expected data made it through to the app for onboarding. When the simulated and live flows matched, testing became far more tractable.

The production release itself was straightforward once testing passed. Since the App Clip is embedded in the main app binary, the only additional task was submitting copy and image assets to App Store Connect for the invocation modal.

Screenshot of App Store Connect screen for uploading copy and image assets.
App Store Connect

Retrospective and Size

The project was laden with unknowns—both the technology itself and its pairing with React Native, which is not the conventional approach for App Clips. The "fail fast and iterate" posture served the team well. Having a developer with native iOS experience also proved invaluable, given how much of the workflow depends on Apple tooling even when the clip is written in React Native.

One surprise was the difficulty of sharing code between the main Shop app and the clip. Sharing imposed overhead that affected the entire application team, so the team opted to copy code where practical.

The final App Clip size came in at 9.1MB, under the 10MB cap but not by a wide margin. The constraint made the project instructive, and the team built most of the planned functionality despite it—with room for further optimization later.

Sebastian Ekström is a Senior Developer based in Stockholm who has been with Shopify since 2018, currently working on the Shop Retention team.