Pourquoi votre application mobile rame sur 4G : le piège des payloads API surdimensionnés
Vous avez passé des semaines à optimiser votre interface Flutter ou React Native. Les animations sont fluides, les widgets sont bien découpés, les performances sur simulateur sont irréprochables. Et pourtant, dès que votre client teste l'application dans le métro sur sa connexion 4G fragile, il se plaint que l'appli « rame » et vide sa batterie.
Le problème n'est pas l'interface. Le problème vient de votre API.
Cet article, inspiré d'un retour d'expérience publié sur DEV.to, détaille les erreurs les plus courantes en matière de conception d'API mobile et vous donne des pistes concrètes pour les corriger — avec un impact direct sur l'expérience utilisateur et sur les coûts d'infrastructure, un point crucial pour les PME.
Le piège de l'over-fetching : envoyer trop de données
La cause numéro un des lenteurs réseau côté API est l'over-fetching : l'API retourne bien plus de données que ce dont l'application a besoin.
Prenons un exemple classique en Symfony. Un développeur crée rapidement un endpoint pour afficher le profil d'un utilisateur :
// ❌ À ne pas faire
#[Route('/api/user/{id}', methods: ['GET'])]
public function getUserProfile(User $user): JsonResponse
{
return $this->json($user);
}
Cette approche sérialise l'intégralité de l'entité : hash du mot de passe, tokens de vérification, timestamps, préférences, colonnes internes… Un payload de 400 à 800 Ko pour afficher simplement un prénom et un avatar.
Sur une connexion stable en bureau, personne ne le remarque. Sur un réseau 4G instable, le téléphone met plusieurs secondes à recevoir, puis plusieurs centaines de millisecondes à parser ce JSON inutilement lourd. L'interface se fige. L'utilisateur frustré désinstalle l'application.
La solution : définir une couche de présentation stricte avec des DTO (Data Transfer Objects) ou des groupes de sérialisation Symfony.
// ✅ Utiliser des groupes de sérialisation
#[Route('/api/user/{id}', methods: ['GET'])]
public function getUserProfile(User $user): JsonResponse
{
return $this->json($user, 200, [], ['groups' => ['user:mobile:read']]);
}
Dans votre entité, vous annotez uniquement les champs nécessaires :
use Symfony\Component\Serializer\Annotation\Groups;
class User
{
#[Groups(['user:mobile:read'])]
public string $firstName;
#[Groups(['user:mobile:read'])]
public string $avatarUrl;
// Le reste n'est jamais exposé à l'application mobile
public string $passwordHash;
public \DateTimeImmutable $emailVerifiedAt;
}
Résultat : un payload de quelques dizaines d'octets au lieu de plusieurs centaines de kilooctets.
Pagination et chargement progressif : ne jamais envoyer une liste complète
Autre erreur fréquente : retourner toutes les entrées d'une collection en une seule réponse.
// ❌ Charger 2000 produits d'un coup
$products = $productRepository->findAll();
return $this->json($products);
Une liste de 2 000 produits avec leurs images, descriptions et métadonnées peut facilement atteindre plusieurs mégaoctets. Sur mobile, c'est une catastrophe réseau et mémoire.
Mettez en place une pagination systématique dès la conception de vos endpoints :
// ✅ Pagination avec Doctrine et KnpPaginatorBundle
#[Route('/api/products', methods: ['GET'])]
public function listProducts(Request $request, PaginatorInterface $paginator): JsonResponse
{
$query = $this->productRepository->createQueryBuilder('p')->getQuery();
$pagination = $paginator->paginate(
$query,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 20) // 20 items max par défaut
);
return $this->json([
'data' => $pagination->getItems(),
'total' => $pagination->getTotalItemCount(),
'page' => $pagination->getCurrentPageNumber(),
'per_page' => $pagination->getItemNumberPerPage(),
], 200, [], ['groups' => ['product:mobile:list']]);
}
L'application mobile charge une première page de 20 résultats, puis déclenche le chargement de la page suivante uniquement quand l'utilisateur atteint le bas de la liste (infinite scroll). L'expérience perçue est bien meilleure et la consommation réseau est drastiquement réduite.
💡 Conseil PME : réduire la taille de vos payloads, c'est aussi réduire votre bande passante sortante et donc vos coûts d'hébergement. Sur des volumes importants, l'économie peut être significative.
Compression et formats légers : des gains immédiats
Activer la compression GZIP
Si vous ne pouvez pas immédiatement refactoriser vos endpoints, activez la compression GZIP au niveau de votre serveur web (Nginx ou Apache). Le gain est immédiat : un JSON de 500 Ko compressé descend souvent à 50-80 Ko.
# Nginx - activer gzip pour les réponses JSON
gzip on;
gzip_types application/json text/plain;
gzip_min_length 1024;
En Symfony, vous pouvez également utiliser le middleware de compression de réponse HTTP via BazingaHateoasBundle ou simplement laisser le serveur gérer cette couche.
Choisir les bons champs à exposer selon le contexte
Une bonne pratique consiste à définir plusieurs groupes de sérialisation selon le contexte d'usage :
product:mobile:list→ id, nom, prix, miniature (liste)product:mobile:detail→ tous les champs affichés sur la fiche produitproduct:admin:full→ tous les champs pour le back-office
Cette granularité évite l'over-fetching sans multiplier les endpoints.
Considérer le format JSON:API ou des projections GraphQL
Pour des applications mobiles avec des besoins de données très variés, des solutions comme GraphQL ou le standard JSON:API permettent à l'application cliente de demander exactement les champs dont elle a besoin. Symfony dispose d'une intégration excellente avec API Platform, qui supporte ces deux approches nativement.
Conclusion : des APIs conçues pour le mobile dès le départ
Les performances d'une application mobile ne se jouent pas uniquement dans le code côté client. La qualité de la conception de votre API backend est au moins aussi déterminante, surtout pour vos utilisateurs sur des connexions mobiles variables.
Retenir trois règles simples :
- N'exposez jamais plus de données que nécessaire — utilisez des groupes de sérialisation ou des DTO dédiés.
- Paginezez toujours vos listes — jamais de
findAll()sur un endpoint public. - Compressez vos réponses — GZIP est gratuit et efficace.
Ces optimisations sont rapides à mettre en œuvre sur une application Symfony existante et ont un impact mesurable immédiat : temps de réponse perçu, taux de rétention utilisateur, et pour les PME, réduction des coûts d'infrastructure.
Si vous souhaitez auditer les performances API de votre application ou concevoir une architecture mobile-first dès le départ, l'équipe MulerTech est à votre disposition.
Cet article s'inspire du retour d'expérience de Prajapati Paresh publié sur DEV.to, adapté ici au contexte PHP/Symfony et aux besoins des PME.