Une fédération sportive nous a confié un projet à deux visages : un site web éditorial classique pour le grand public, et une application mobile pour les licenciés, avec des notifications, un calendrier de compétitions et des résultats en direct. Deux équipes différentes, deux calendriers de livraison différents, mais un seul back-office de contenu : hors de question de dupliquer la saisie des articles et des résultats à deux endroits.
L’architecture retenue repose sur un principe simple : une seule instance WordPress, une API versionnée, et des contrats de champs différents selon le client qui interroge.
Un espace de noms d’API par génération de contrat
Les routes personnalisées de la fédération vivent sous un espace de noms propre, federation/v1, distinct du cœur wp/v2. Quand un changement de structure incompatible devient nécessaire (un champ renommé, un format de date modifié), on introduit federation/v2 plutôt que de casser silencieusement l’application mobile déjà publiée sur les stores, dont la mise à jour prend plusieurs semaines à se propager chez tous les utilisateurs.

Cette discipline de versionnement protège le client le plus lent à évoluer. Le site web se redéploie en quelques minutes ; une application mobile, elle, reste installée dans des versions anciennes pendant des mois. Tant qu’il existe des utilisateurs de la première version de l’application, federation/v1 doit continuer à répondre exactement comme avant.
On prépare la fin de vie d’un espace de noms en la signalant dans les réponses. Le filtre rest_post_dispatch permet d’ajouter un en-tête Sunset, défini par la RFC 8594, que l’équipe mobile peut surveiller :
add_filter( 'rest_post_dispatch', 'federation_signaler_fin_de_vie', 10, 3 );
function federation_signaler_fin_de_vie( $reponse, $serveur, $requete ) {
if ( 0 === strpos( $requete->get_route(), '/federation/v1/' ) ) {
$reponse->header( 'Sunset', 'Wed, 31 Dec 2025 23:59:59 GMT' );
$reponse->header( 'Link', '</wp-json/federation/v2/>; rel="successor-version"', false );
}
return $reponse;
}
Le troisième argument false de la méthode header() ajoute l’en-tête sans remplacer un éventuel en-tête Link déjà présent, celui de la pagination par exemple.
Laisser chaque client demander ce dont il a besoin
La REST API de WordPress propose nativement le paramètre _fields, qui limite la réponse aux champs demandés. Le site web, qui affiche une liste d’articles, n’a besoin que de quelques champs ; l’application, qui affiche un écran de calendrier, en demande d’autres :
# Le site web : liste d'articles
curl "https://federation.example/wp-json/wp/v2/posts?per_page=10&_fields=id,date,link,title.rendered,excerpt.rendered"
# L'application : calendrier des compétitions
curl "https://federation.example/wp-json/wp/v2/competition?per_page=50&_fields=id,title.rendered,mobile"
Chaque consommateur télécharge donc l’essentiel, et rien de plus : sur un réseau mobile, la taille de la réponse se traduit directement en temps d’affichage. Les en-têtes X-WP-Total et X-WP-TotalPages de la réponse permettent à l’application de paginer sans jamais demander la liste complète.
Des champs dédiés aux besoins du mobile
Certaines données n’ont de sens que pour l’application : l’état « en direct » d’une épreuve, l’heure de début au format attendu par l’agenda du téléphone, ou l’identifiant à transmettre au service de notifications. Plutôt que de surcharger les champs communs, on les regroupe dans un champ dédié, déclaré avec register_rest_field() :
add_action( 'rest_api_init', 'federation_champ_mobile' );
function federation_champ_mobile() {
register_rest_field( 'competition', 'mobile', array(
'get_callback' => function ( $objet ) {
return array(
'debut_iso' => gmdate( 'c', (int) get_post_meta( $objet['id'], 'debut_timestamp', true ) ),
'lieu' => (string) get_post_meta( $objet['id'], 'lieu', true ),
'en_direct' => (bool) get_post_meta( $objet['id'], 'en_direct', true ),
);
},
'schema' => array(
'description' => 'Données spécifiques à l\'application mobile.',
'type' => 'object',
'context' => array( 'view' ),
'readonly' => true,
),
) );
}
Ce champ s’ajoute à la réponse standard du type de contenu competition, à condition que celui-ci soit enregistré avec show_in_rest à vrai. Le site web l’ignore en n’utilisant pas _fields pour le réclamer ; l’application le demande explicitement. Le back-office reste unique : les rédacteurs saisissent une épreuve une fois, et les deux fronts la consomment.
Une API partagée n’est pas une API identique pour tous : c’est un contrat stable, dont chaque client lit seulement la partie qui le concerne.
Les décisions qui coûtent cher si on les repousse
- L’authentification. Ne glissez jamais d’identifiants d’administrateur dans l’application : le paquet se décompile. Pour les lectures publiques, aucun jeton n’est nécessaire ; pour les données d’un licencié, utilisez une authentification propre à l’utilisateur, avec des droits limités.
- La mise en cache. Les résultats en direct changent toutes les minutes, le calendrier toutes les semaines : distinguez-les par des durées de cache différentes, configurées au niveau du serveur ou de la couche de diffusion.
- Les notifications. WordPress n’envoie pas de notifications poussées : c’est l’événement de publication (le crochet
transition_post_status, par exemple) qui déclenche l’appel au service de diffusion choisi. - La suppression de champs. Retirer ou renommer un champ d’un espace de noms publié casse les applications déjà installées : ajoutez, ne retirez pas, ou passez à une nouvelle version.
Un protocole de recette pour deux fronts
Avant chaque livraison du back-office, exécutez une suite de requêtes de contrôle, une par route utilisée par chaque front, et comparez les réponses à des instantanés de référence. Un test automatique qui vérifie la présence et le type des champs attendus par l’application suffit à détecter la plupart des régressions avant qu’elles n’atteignent les stores. C’est peu de travail pour protéger une version mobile que l’on ne peut pas corriger en quelques minutes.
Conclusion
Nourrir un site et une application avec un seul WordPress repose sur trois principes : des espaces de noms versionnés pour ne jamais casser les clients installés, des requêtes qui ciblent exactement les champs nécessaires, et des champs dédiés pour les besoins propres au mobile. Le back-office reste unique, la saisie n’est jamais dupliquée, et chaque équipe avance à son rythme sur un contrat qu’elle comprend.