Ajouter un App Clip à une application Expo
Ajoute une cible iOS App Clip à un projet Expo. Le Clip réside dans targets/clip/, est distribué avec l'application parent, et est invoqué à partir d'une URL du domaine de l'application via un fichier Apple App Site Association (AASA).
L'identifiant de bundle de l'application parent devient com.<username>.<app-name> et celui du Clip est automatiquement dérivé comme <parent>.clip (ex. com.bacon.may20.clip).
1. Définis bundleIdentifier et appleTeamId
bun create target avertit si ces paramètres manquent. Ajoute à app.json :
{
"expo": {
"ios": {
"bundleIdentifier": "com.<username>.<app-name>",
"appleTeamId": "XX57RJ5UTD"
}
}
}
2. Ajoute la cible App Clip
bun create target clip
Cela installe @bacons/apple-targets, l'ajoute au tableau plugins dans app.json, et crée :
targets/clip/expo-target.config.js— le plugin de configuration de la cibletargets/clip/Info.plist— Info.plist du Cliptargets/clip/AppDelegate.swift,Assets.xcassets, etc.
Choisis une bonne icône ou réutilise celle existante définie dans l'application — vérifie-la avec bunx expo config sous la clé icon ou ios.icon.
3. Configure les domaines associés
L'application parent et le Clip ont tous deux besoin de l'entitlement Associated Domains pointant vers le domaine qui héberge le fichier AASA.
Dans app.json, ajoute à la fois les entrées applinks: (parent) et appclips: (invocation du Clip) :
{
"expo": {
"ios": {
"associatedDomains": [
"applinks:may20.expo.app",
"appclips:may20.expo.app"
]
}
}
}
Dans targets/clip/expo-target.config.js, déclare l'entitlement du Clip :
/** @type {import('@bacons/apple-targets/app.plugin').ConfigFunction} */
module.exports = (config) => ({
type: "clip",
icon: "https://github.com/expo.png",
entitlements: {
"com.apple.developer.associated-domains": ["appclips:may20.expo.app"],
},
});
Si tu passes cette étape,
expo prebuildaffichera :Apple App Clip may require the associated domains entitlement but none were found.
4. Enregistre les identifiants de bundle et crée l'entrée App Store
bunx setup-safari
Cela se connecte au compte Apple Developer, enregistre com.bacon.may20, crée l'entrée App Store Connect, et affiche :
- Un JSON
apple-app-site-associationde démarrage - Une balise
<meta name="apple-itunes-app">avec l'ID iTunes de l'application - Team ID, iTunes ID, et Bundle ID
5. Héberge le fichier AASA
Les App Clips sont invoqués quand iOS récupère https://<your-domain>/.well-known/apple-app-site-association et trouve une entrée appclips correspondante.
mkdir -p public/.well-known
touch public/.well-known/apple-app-site-association
Colle le JSON que setup-safari a affiché, mais ajoute un bloc appclips pour l'ID complet de l'application Clip (<TeamID>.<ClipBundleID>). La sortie de setup-safari couvre uniquement l'application parent :
{
"applinks": {
"details": [
{
"appIDs": ["XX57RJ5UTD.com.bacon.may20"],
"components": [{ "/": "*", "comment": "Matches all routes" }]
}
]
},
"appclips": {
"apps": ["XX57RJ5UTD.com.bacon.may20.clip"]
},
"activitycontinuation": {
"apps": ["XX57RJ5UTD.com.bacon.may20"]
},
"webcredentials": {
"apps": ["XX57RJ5UTD.com.bacon.may20"]
}
}
Notes :
- Le fichier n'a pas d'extension et aucune exigence de
Content-Typeau-delà d'être servi tel quel. L'export statique Expo Router sert les fichiers danspublic/verbatim. - Le bloc
appclipsest ce qui permet à une URL du domaine de lancer le Clip. webcredentialsest utilisé pour partager les identifiants entre le site, l'application parent et l'App Clip.activitycontinuationest optionnel et utilisé pour partager le lien entre mobile et desktop. Doit être utilisé avecHeaddepuis expo-router — voir https://docs.expo.dev/router/advanced/apple-handoff/- Notation et détails de désactivation des routes : https://sosumi.ai/documentation/xcode/supporting-associated-domains
6. Ajoute la balise Smart App Banner meta
Crée src/app/+html.tsx (le shell HTML d'Expo Router) et ajoute la balise de setup-safari. Crée le modèle versionné s'il n'existe pas :
bunx expo customize src/app/+html.tsx
Ajoute la balise meta au <head> :
import { ScrollViewStyleReset } from "expo-router/html";
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="apple-itunes-app" content="app-id=6771566491" />
<ScrollViewStyleReset />
</head>
<body>{children}</body>
</html>
);
}
Pour que le site affiche la carte App Clip au lieu de la carte d'installation, utilise :
<meta
name="apple-itunes-app"
content="app-id=6771566491, app-clip-bundle-id=com.bacon.may20.clip, app-clip-display=card"
/>
7. Déploie le site
Le fichier AASA doit être en ligne avant qu'iOS ne fasse confiance à l'association. Utilise EAS Hosting :
bunx expo export -p web
eas deploy --prod
Cela publie le site (y compris /.well-known/apple-app-site-association) à https://<slug>.expo.app. Vérifie :
curl https://may20.expo.app/.well-known/apple-app-site-association
8. Miroir les permissions
Inspecte les permissions de l'application parent après prebuild :
npx expo config --type introspect
Regardes l'objet infoPlist — miroir les clés de permission dans le Info.plist du App Clip afin que les APIs correspondantes puissent être utilisées depuis le Clip.
Définis deploymentTarget: "17.6" dans la configuration cible du Clip — les App Clips ont une limite de taille minimale plus élevée dans iOS 17.6.
Si l'application utilise les notifications push ou les services de localisation, ajoute au Info.plist du App Clip pour demander les permissions nécessaires :
<key>NSAppClip</key>
<dict>
<key>NSAppClipRequestEphemeralUserNotification</key>
<false/>
<key>NSAppClipRequestLocationConfirmation</key>
<true/>
</dict>
9. Build et soumets à TestFlight
bunx testflight
Cela va :
- Générer un
eas.jsons'il manque. - Configurer les identifiants pour les deux cibles (parent + Clip). Chacune obtient son propre profil de provisionnement mais peuvent partager un seul certificat de distribution.
- Synchroniser les capacités — note
Enabled: Associated Domainspour la cible Clip. - Build, télécharger, et programmer une soumission TestFlight.
10. Configure les métadonnées App Clip
Récupère les métadonnées App Store existantes en local :
eas metadata:pull
Ajoute apple.appClip à store.config.json. Jusqu'à 3 URL d'invocation peuvent lancer le Clip depuis une page web :
{
"configVersion": 0,
"apple": {
"appClip": {
"defaultExperience": {
"action": "PLAY",
"releaseWithAppStoreVersion": true,
"reviewDetail": {
"invocationUrls": ["https://may20.expo.app/", null, null]
},
"info": {
"en-US": {
"subtitle": "Instantly native with Expo",
"headerImage": "store/apple/app-clip/en-US/asc-app-clip.png"
}
}
}
}
}
}
Le headerImage doit être un PNG 1800×1200 sans opacité.
Renvoie les changements au store :
eas metadata:push
Directives de métadonnées App Clip recommandées par Apple : https://sosumi.ai/documentation/appclip/configuring-the-launch-experience-of-your-app-clip
Ce que tu obtiens
- Cible d'application parent :
com.bacon.may20 - Cible App Clip :
com.bacon.may20.clip, réside danstargets/clip/ - AASA hébergé à
https://may20.expo.app/.well-known/apple-app-site-association - Balise Smart App Banner meta sur chaque route web
- Chaque route liée à son équivalent natif
- Build TestFlight de l'application parent avec le Clip embarqué
Une fois qu'Apple invoque le Clip à partir d'une URL du domaine, iOS ouvre le point d'entrée de targets/clip/ qui charge l'application React Native.
Détection native (optionnel)
Pour permettre à JS de détecter quand il s'exécute à l'intérieur d'un App Clip et présenter une invite d'installation pour l'application complète, crée un module Expo local (bunx create-expo-module --local) qui expose navigator.appClip.prompt().
Voir ./references/native-module.md pour le module Swift, l'interface TypeScript, et l'utilisation.
Références
- ./references/native-module.md — Module Expo local pour détecter le contexte App Clip et présenter l'invite d'installation SKOverlay