Skip to main content

Command Palette

Search for a command to run...

Building an Apple App Clip with React Native & Expo

What Worked, What Broke, and Lessons Learned

Updated
7 min readView as Markdown
Building an Apple App Clip with React Native & Expo
Y
Crafting delightful applications with #React #ReactNative #NextJS #TypeScript #GraphQL

When building a mobile application, one of the biggest conversion bottlenecks is download friction.

If someone comes to our website svastha.co through Safari, a social post, or an iMessage link, we'd like to provide a seamless experience for them to download the app and start the experience.

That led us to Apple App Clips: instant, bite-sized mini-apps that launch card previews on iOS without downloading the full binary.

While the end-user experience is magical, building an App Clip inside a modern React Native (Expo) codebase turned out to be an engineering obstacle course. Here is a breakdown of how the architecture works, the roadblocks we hit, how we solved provisioning profile mismatches in Apple Developer Portal, and the key lessons learned.

The Architecture: How App Clips Work with React Native

An App Clip is not a web view, nor is it a separate App Store listing. It is a secondary native target (your.bundle.id.Clip) embedded inside your main application's .ipa bundle.

Using an Expo config plugin like react-native-app-clip, the build toolchain automates:

  • Creating the Xcode target (YourAppClip) alongside the main app.

  • Generating dedicated Info.plist and .entitlements files for the clip.

  • Setting up CocoaPods autolinking for the App Clip target.

In JavaScript / TypeScript, your root component checks whether it is running within the clip target:

import { isClip, displayOverlay } from "react-native-app-clip";
import AppClipScreen from "./src/clip/AppClipScreen";

export default function App() {
  if (isClip()) {
    return <AppClipScreen />;
  }
  return <FullAppNavigator />;
}

The user instantly sees the requested content with zero sign-up friction. If they want deeper features (such as saved preferences, account sync, or notifications), tapping a button invokes Apple’s native SKOverlay:

displayOverlay(); // Native iOS App Store sheet slides up from the bottom!

Lesson 1: The App Clip Size Trap (Error ITMS-90865)

The Problem

During submission to App Store Connect, Apple rejected the binary with:

ITMS-90865: Thinned App Clip size is too large — The main bundle of the App Clip is 46 MB, which exceeds the maximum allowable size. After app thinning, the main bundle of any App Clip variant must be less than 10 MB.

Why Did This Happen?

  • Apple's Historic Limits: For iOS 15 and iOS 16, Apple limits the uncompressed main binary of an App Clip to 10 MB – 15 MB.

  • The Weight of React Native: Under modern React Native with the New Architecture and Hermes, the underlying prebuilt frameworks (React.framework, hermesvm.framework, and runtime dependencies) easily exceed 40 MB uncompressed.

  • When your build tool links React Native and Hermes into the App Clip target, the uncompressed payload reaches 40–50 MB, instantly triggering Apple's rejection.

The Fix

Apple expanded App Clip limits starting in iOS 17.0: for digital invocations (Safari Smart App Banners and universal links), the maximum allowable App Clip size was increased to 100 MB.

In app.json, configure your App Clip plugin with deploymentTarget: "17.0":

[
  "react-native-app-clip",
  {
    "name": "Your App Clip Name",
    "targetSuffix": "Clip",
    "bundleIdSuffix": "Clip",
    "groupIdentifier": "group.your.bundle.id",
    "deploymentTarget": "17.0"
  }
]

By raising the App Clip target to 17.0 (while leaving your main app's deployment target at iOS 15.1+), the 46 MB bundle easily qualifies under the 100 MB allowance. (Users on iOS 16 or earlier simply open your web experience or main App Store page).

Lesson 2: EAS CLI & Apple Capability Sync Failures

The Problem

When running an automated EAS build, the build crashed during the credential setup phase:

Failed to patch capabilities: [
  { capabilityType: 'ONDEMANDINSTALL_EXTENSIONS', option: 'ON' },
  { capabilityType: 'APP_GROUPS', option: 'OFF' }
]
✖ Failed to sync capabilities
Apple API error: The request entity is not a valid request document object - Unexpected or invalid value at 'data.relationships.bundleIdCapabilities.data.[0].attributes'

Why Did This Happen?

When EAS Build prepares an iOS build, it calls Apple’s App Store Connect API to auto-configure capabilities on every registered Bundle Identifier.

App Clips have unique constraints:

  1. They require a parentBundleId relationship in the Apple API that automated CLI patchers often omit.

  2. Apple’s API schema for App Clip capabilities like ONDEMANDINSTALL_EXTENSIONS differs from standard apps.

The Fix

Bypass automated capability syncing by passing EXPO_NO_CAPABILITY_SYNC=1:

EXPO_NO_CAPABILITY_SYNC=1 npx eas-cli build --platform ios ...

Add this flag directly to your package.json release scripts:

"release:ios": "npm run release:preflight:ios && EXPO_NO_CAPABILITY_SYNC=1 npx eas-cli build --platform ios --profile production --auto-submit --non-interactive"

You can then configure capabilities manually in the Apple Developer Portal once, and builds will proceed without API schema conflicts.

Lesson 3: Certificate Mismatch & Provisioning Profile Regeneration

When adding an App Clip or modifying an existing App ID's capabilities, Apple flags:

"Adding or removing any capabilities will invalidate any provisioning profiles that include this App ID and they must be regenerated for future use."

If your distribution certificates or provisioning profiles fall out of sync, your build will fail in Fastlane with:

Provisioning profile doesn't include signing certificate "Apple Distribution: ...".
Provisioning profile doesn't include the com.apple.developer.associated-domains entitlement.

How to Regenerate Profiles and Sync Credentials Cleanly

Step 1: Align Your Distribution Certificate in Apple Developer Portal

  1. Go to Apple Developer Portal > Certificates, Identifiers & Profiles > Profiles.

  2. If your account switched from an Individual to an Organization/Company account, ensure all active Provisioning Profiles are linked to the current Company Distribution Certificate.

  3. Open your App ID (your.bundle.id) and App Clip ID (your.bundle.id.Clip) and verify that required capabilities (e.g. Associated Domains and App Groups) are checked and saved.

Step 2: Regenerate via EAS Credentials Manager

Rather than guessing which local cert matches:

  1. Run:

    EXPO_NO_CAPABILITY_SYNC=1 npx eas-cli credentials
    
  2. Select iOS > production (or your active build profile).

  3. Select Provisioning Profile > Delete Provisioning Profile.

  4. Select Generate a new Apple Provisioning Profile > choose Yes.

  5. Repeat for the App Clip target if prompted.

  6. EAS will pull the updated App ID configuration from Apple Developer, bind it to your active Distribution Certificate, and cache the new .mobileprovision file.

Lesson 4: Web-to-Clip Handshake (apple-app-site-association)

An App Clip will not trigger from the web unless the domain handshake is complete:

1. Smart App Banner Meta Tag

In your website’s HTML <head>:

<meta
  name="apple-itunes-app"
  content="app-id=YOUR_APP_APPLE_ID, app-clip-bundle-id=your.bundle.id.Clip, app-clip-display=card"
/>

2. Apple App Site Association (AASA)

Hosted with Content-Type: application/json at https://yourdomain.com/.well-known/apple-app-site-association:

{
  "appclips": {
    "apps": ["<TEAM_ID>.your.bundle.id.Clip"]
  },
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "<TEAM_ID>.your.bundle.id",
        "paths": ["*", "/shared/*"]
      }
    ]
  }
}

3. iOS Associated Domains Entitlement

In app.json:

"associatedDomains": [
  "applinks:yourdomain.com",
  "appclips:yourdomain.com"
]

Summary Checklist

Topic Solution
Size Exceeded (ITMS-90865) Set deploymentTarget: "17.0" in plugin options to use Apple's 100 MB digital invocation limit for React Native / Hermes bundles.
Capability Sync API Failure Add EXPO_NO_CAPABILITY_SYNC=1 to your build script so EAS skips fragile capability patching.
Invalidated Profiles Delete outdated provisioning profiles in eas-cli credentials and let EAS regenerate fresh profiles against your current Distribution Certificate.
Apple Review Guidelines Ensure your App Clip delivers instant utility without a mandatory login barrier (Apple will reject clips that block users behind an auth wall).