Les grilles tarifaires françaises te donnent des euros. Inutilisable quand tu veux comprendre pourquoi un projet dérape.
On a repris le suivi de temps de nos derniers projets mobiles et on a tout reventilé en heures par brique fonctionnelle. Pas en euros : les heures, elles, ne dépendent pas du TJM de celui qui te fait le devis.
Verdict court : ce qui coûte cher n'est presque jamais ce que le client croit.
La ventilation réelle, en heures
Base : une app React Native + Expo, back-end Supabase, iOS + Android, un projet type de ~700 h.
| Brique | Heures | Part |
|---|---|---|
| Auth (email + OAuth + reset + refresh token) | 45 | 6 % |
| Écrans de liste / détail / formulaires (×18 écrans) | 160 | 23 % |
| Notifications push (serveur + client + deep links) | 55 | 8 % |
| Paiement in-app (StoreKit + Play Billing + réconciliation serveur) | 90 | 13 % |
| Offline-first + synchronisation | 130 | 19 % |
| Back-office admin | 70 | 10 % |
| Tests, CI, publication stores, allers-retours review | 95 | 14 % |
| Cadrage, suivi, corrections post-recette | 55 | 8 % |
Deux lignes pèsent 32 % du projet à elles seules : offline-first et paiement. Ce sont aussi les deux que les clients citent en dernier, souvent en fin de réunion, avec un « ah oui, et il faudra que ça marche dans le métro ».
Pourquoi l'offline coûte 130 h
Parce que ce n'est pas une fonctionnalité, c'est une décision d'architecture qui contamine chaque écran.
Un fetch classique :
const { data } = await supabase.from('tasks').select('*')
setTasks(data)
La même chose en offline-first, version honnête :
// 1. lecture locale immédiate
const local = await db.query('SELECT * FROM tasks WHERE deleted_at IS NULL')
setTasks(local)
// 2. push des mutations en attente
const pending = await db.query('SELECT * FROM outbox ORDER BY created_at')
for (const op of pending) {
const res = await supabase.rpc('apply_op', { op })
if (res.error?.code === 'CONFLICT') {
await resolveConflict(op, res.error.details) // ← le vrai chantier
}
await db.run('DELETE FROM outbox WHERE id = ?', op.id)
}
// 3. pull incrémental
const since = await getLastSyncedAt()
const { data: remote } = await supabase
.from('tasks')
.select('*')
.gt('updated_at', since)
await mergeIntoLocal(remote)
resolveConflict est la ligne qui coûte. Deux appareils modifient la même entité hors ligne : qui gagne ? Last-write-wins est simple et faux dès qu'il y a de l'argent ou du stock. Un merge par champ demande un modèle de données pensé pour, donc une reprise de toutes les tables.
Ajoute la suppression logique (deleted_at partout, sinon un enregistrement supprimé ressuscite à la synchro suivante), les migrations du schéma local sur des appareils qui ont sauté trois versions, et une file d'attente qui survit à un kill de l'app.
Le piège qu'on a mangé : on a livré une première version avec un outbox en mémoire. Un utilisateur ferme l'app dans le tunnel, ses 6 dernières actions disparaissent. Silencieusement. Détecté trois semaines après la mise en production, par recoupement de logs. Rattrapage : 22 h.
Pourquoi le paiement coûte 90 h
Le SDK, c'est une demi-journée. Le reste, c'est la réconciliation.
Un achat validé côté client ne prouve rien. Il faut vérifier le reçu côté serveur, gérer les webhooks de renouvellement, les remboursements, les périodes de grâce, les essais gratuits, les changements de plan, et le cas où l'utilisateur a acheté sur iOS puis se connecte sur Android.
// webhook Apple — l'état d'abonnement ne vient JAMAIS du client
const notif = await verifyAppleSignedPayload(req.body.signedPayload)
switch (notif.notificationType) {
case 'DID_RENEW':
case 'SUBSCRIBED':
await setEntitlement(notif.originalTransactionId, 'active', notif.expiresDate)
break
case 'DID_FAIL_TO_RENEW': // grâce, pas révocation immédiate
await setEntitlement(notif.originalTransactionId, 'grace', notif.gracePeriodExpiresDate)
break
case 'REFUND':
await revokeAndAudit(notif.originalTransactionId)
break
}
Multiplie par deux : Google Play a sa propre sémantique, ses propres états, son propre vocabulaire. Deux implémentations, deux jeux de tests, deux environnements de sandbox capricieux.
Piège rencontré : les sandbox Apple compressent les durées d'abonnement (un mois = quelques minutes). Un test de renouvellement écrit sans en tenir compte passe au vert en sandbox et échoue en production. On l'a découvert sur un utilisateur réel qui a perdu son accès premium après 30 jours.
Où l'assistance IA fait gagner du temps — et où elle n'en fait pas gagner
On travaille quotidiennement avec Claude Code. Les gains sont réels mais très inégalement répartis, et ça recoupe précisément les données publiées.
D'après le rapport DORA (Google Cloud), qui cite les travaux de Stanford : 35-40 % de gain sur les tâches simples et sur du code neuf, moins de 10 % sur du legacy complexe. Le même rapport mesure +20 % de pull requests par auteur en un an, et une adoption autour de 90 %. Stack Overflow 2025 donne 84 % d'utilisateurs, 51 % au quotidien.
Notre propre répartition, sur les briques du tableau plus haut :
| Brique | Gain observé |
|---|---|
| Écrans liste/détail/formulaires | ~40 % |
| Back-office admin | ~40 % |
| Auth | ~30 % |
| Tests et CI | ~25 % |
| Notifications push | ~15 % |
| Paiement | ~10 % |
| Offline-first / conflits | quasi nul |
La logique est nette : plus la brique est standard, plus le gain est fort. Générer 18 écrans CRUD cohérents avec le design system, c'est du temps massivement récupéré. Décider de la stratégie de résolution de conflits sur ton modèle métier, c'est un travail de conception que personne ne fait à ta place.
Le corollaire qui intéresse le client : le gain porte sur les postes qui ne pesaient déjà pas très lourd. Il déplace le prix, il ne l'effondre pas.
Le contre-point, à citer honnêtement. L'essai randomisé METR de juillet 2025 (arXiv 2507.09089) avait mesuré des développeurs expérimentés 19 % plus lents avec l'IA, alors qu'ils s'estimaient 20 % plus rapides. METR a reconnu en février 2026 une faille méthodologique (biais de sélection). Le chiffre ne tient donc plus tel quel — mais l'écart perception/réalité, lui, reste un bon rappel : mesure ton temps, ne l'estime pas.
Ce qu'on en fait concrètement
Trois réflexes qu'on applique en cadrage :
- Demander « offline ? » et « paiement ? » à la minute 5, pas à la fin. Ces deux réponses déterminent un tiers du budget.
- Chiffrer en heures par brique, jamais en forfait global. Le client voit ce qu'il achète et peut arbitrer ligne par ligne.
- Sortir l'offline-first de la v1 quand c'est possible. Un cache lecture seule + un message clair couvre 80 % des usages pour 15 h au lieu de 130.
Si tu veux la traduction en euros, la ventilation complète et le coût sur 3 ans : Prix développement application mobile 2026 : les vrais chiffres.
Et sur notre outillage : créer une application avec Claude Code.
Vous avez mesuré vos briques ? Vos ratios diffèrent des nôtres ? Les commentaires sont faits pour ça.
Top comments (0)