De WordPress à Vue.js : repenser l’interface sans toucher au backend
Dans le premier volet, « Construire un plugin WordPress avec l’API REST : exposer les données avant l’interface », nous avons posé une idée simple : les données ne sont pas l’interface. Avant toute question d’affichage, il s’agissait surtout de rappeler ce que WordPress sait faire nativement : structurer des contenus et les exposer de manière fiable. En choisissant l’API REST pour diffuser la liste des articles, l’interface a volontairement été mise de côté. Non par goût de l’abstraction, mais pour clarifier les rôles. WordPress reste le socle : il garantit la cohérence des contenus et leur stabilité. L’interface, elle, peut évoluer sans remettre ce socle en question.
Cette séparation relève d’une pédagogie concrète. WordPress expose les données, l’interface se charge de les afficher. En respectant cette frontière, on se donne la liberté de faire évoluer l’affichage sans fragiliser le backend ni repartir de zéro. C’est précisément l’objet de ce second article : faire évoluer l’interface sans toucher aux données. Le plugin reste inchangé, les routes REST aussi. Ce qui change, c’est la manière de présenter l’information. Nous allons progressivement quitter une logique d’affichage procédurale pour adopter une écriture plus lisible et plus déclarative, en nous appuyant sur Vue.js.
L’objectif n’est pas d’ajouter un outil, mais de montrer comment une interface peut mieux traduire l’intention, à partir de données déjà bien exposées côté WordPress.
Quand le JavaScript impératif montre ses limites
Dans la première version du plugin, l’interface repose sur un JavaScript classique : on parcourt le DOM, on injecte des lignes dans un tableau, puis on réagit aux actions de l’utilisateur. Cette approche est naturelle et suffisante tant que l’affichage reste simple. Les choses se compliquent dès que l’interface évolue. Une recherche, un filtrage ou un simple état de chargement suffisent à multiplier les conditions et les sélecteurs. Le code fonctionne toujours, mais il devient plus long, plus fragmenté, et surtout moins lisible.
Il ne s’agit pas d’un mauvais usage du JavaScript, mais d’une limite fréquente d’un code très directif lorsque les interfaces gagnent en richesse. À ce stade, le besoin devient clair : penser l’interface à partir des données qu’elle affiche, plutôt qu’à partir des manipulations du DOM.
Changer de posture en partant des données
Face à cette complexité croissante, il devient utile de changer de point de vue. Plutôt que de décrire, étape par étape, ce que le navigateur doit modifier, on peut partir des données elles-mêmes et indiquer comment elles doivent être présentées. C’est dans ce contexte que se pose la question de l’outil. Plusieurs approches sont possibles : une organisation JavaScript plus rigoureuse, des bibliothèques légères comme Alpine.js, ou des frameworks orientés interface comme React, Angular ou Svelte. Ici, notre choix s’est porté sur Vue.js, non comme solution universelle, mais comme outil lisible et adapté à une intégration progressive dans WordPress.

Avec cette approche, l’interface cesse d’être une suite d’instructions dispersées. Elle se construit directement à partir des données disponibles. Quand ces données changent, l’affichage suit naturellement. Vue.js s’inscrit alors comme une couche d’interface, et uniquement comme telle. Il ne remplace ni WordPress ni l’API REST existante. Il s’appuie sur des données déjà exposées côté serveur et permet de construire une interface plus claire, plus souple et plus facile à faire évoluer.
Reprendre le plugin existant sans toucher à l’API REST
Pour bien situer ce chapitre, il est important de le replacer dans la continuité de l’article précédent. Nous y avons détaillé la structure générale du plugin, les fichiers qui le composent, ainsi que l’endpoint REST exposé par WordPress. Ce socle existe déjà, il est fonctionnel, et il n’est pas question d’y toucher. L’objectif ici n’est donc pas de modifier l’API, mais de faire évoluer la manière dont ses données sont exploitées côté interface. Concrètement, le fichier rest-endpoints.php et le endpoint /wp-json/pem/v8/blog restent strictement identiques. Toute la suite du travail se concentre sur l’interface JavaScript, avec l’idée de remplacer progressivement une logique DOM classique par une approche pilotée par Vue.js.
Ce recentrage sur l’interface s’accompagne d’un choix assumé. Les fichiers table.js et table.css, hérités de l’implémentation précédente, ne constituent plus une base pertinente dans ce nouveau contexte. Leur mise de côté permet de repartir sur un périmètre clair et cohérent, afin d’introduire Vue.js sans mélange de logiques ni ambiguïté sur le rôle de chaque couche.
Monter Vue.js dans notre plugin WordPress
Nous commençons par intégrer Vue.js de la manière la plus directe possible, en le chargeant depuis un CDN. Cette approche permet de se concentrer sur l’essentiel, sans outillage supplémentaire ni configuration complexe. Comme pour les autres scripts du plugin, l’appel est placé dans le fichier pem-articles-table.php, qui centralise déjà le chargement des ressources de l’interface.
function pem_articles_filtres_enqueue_assets() {
// autres appels
wp_enqueue_script(
'vue-js',
'https://unpkg.com/vue@3/dist/vue.global.prod.js',
array(),
'3.4.0',
true
);
}Vue est maintenant disponible dans la page, exactement comme n’importe quelle autre bibliothèque JavaScript chargée par WordPress. Aucun outil supplémentaire n’est requis, il suffit d’avoir le script présent pour pouvoir commencer à s’en servir. À ce stade, rien ne s’affiche encore : aucune interface n’est construite et aucune donnée n’est manipulée. Nous préparons simplement le point d’ancrage de l’application.
La première étape côté JavaScript consiste donc à poser une base volontairement minimale dans le fichier table.js. L’idée n’est pas de construire tout de suite l’interface, mais de créer une application Vue et de la relier clairement à une zone précise de la page. Ce rôle est confié au conteneur #pem-articles-table, déjà généré par WordPress via le shortcode du plugin.
const createApp = Vue.createApp;
createApp({
data() {
return {};
}
}).mount('#pem-articles-table');
Ce fragment a un rôle volontairement limité : relier Vue à une zone bien définie de la page. Il permet de fixer clairement le périmètre d’action de l’application, qui n’agira que dans ce conteneur et nulle part ailleurs. À ce stade, aucune logique métier n’est encore en place, le tableau n’est pas affiché et les données issues de l’API REST ne sont pas encore demandées.
Connecter Vue.js à l’API REST et afficher les données
Vue étant maintenant en place, nous pouvons passer à l’étape suivante : établir le lien avec WordPress. Il ne s’agit pas encore de travailler l’interface ou d’ajouter des interactions, mais simplement de faire circuler les données entre l’API REST et Vue, de manière claire et lisible. L’API présentée dans le premier article existe déjà, elle est définie dans le fichier rest-endpoints.php et renvoie une liste d’articles prête à être consommée. Rien ne change donc côté serveur.
Nous allons pouvoir interroger le même endpoint depuis Vue. Pour recevoir les données, nous prévoyons une variable articles, dont le rôle va se limiter à stocker le résultat renvoyé par l’API, sans transformation ni traitement particulier. Cette variable est déclarée dès l’initialisation de l’application Vue, afin de préparer la suite, si vous le souhaitez, voir Comprendre data() dans Vue.js.
const createApp = Vue.createApp;
createApp({
data() {
return {
articles: []
};
}
}).mount('#pem-articles-table');
Tout est prêt, nous pouvons maintenant déclencher la récupération des données. Celle-ci intervient une fois l’application attachée à la page, au moment où Vue est prêt à agir. Pour cela, nous utilisons la méthode mounted(), appelée automatiquement lorsque l’application est reliée au DOM. Si besoin, voir Comprendre mounted() dans Vue.js
createApp({
data() {
return {
articles: []
};
},
mounted() {
fetch('/wp-json/pem/v8/blog')
.then(response => response.json())
.then(data => {
this.articles = data;
console.log(this.articles);
});
}
}).mount('#pem-articles-table');
Le cheminement reste simple. Vue demande les données, WordPress les renvoie au format JSON, la promesse est résolue, et le résultat est directement stocké tel quel dans la variable articles. À ce stade, rien ne s’affiche encore à l’écran, mais les données sont bien présentes et prêtes à être utilisées, et pour contrôler, nous pouvons les afficher dans la console.
La question qui se pose alors est simple : où et comment ces données doivent-elles s’afficher dans la page ? La réponse passe par le shortcode WordPress. Son rôle n’est pas de construire une interface dynamique, mais de fournir une structure HTML claire servant de point d’ancrage à Vue. Le shortcode génère ainsi le balisage et la structure nécessaires pour accueillir les données : un conteneur principal et l’ossature du tableau, pensés pour être adaptés et enrichis progressivement au fil des besoins, Vue se chargeant ensuite d’en exploiter le contenu.
add_shortcode('pem_articles_table', function () {
return '
<div id="pem-articles-table">
<table class="pem-articles-table">
<thead>
<tr>
<th>Titre</th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{ article.title }}</td>
</tr>
</tbody>
</table>
</div>';
});Ce HTML est généré par WordPress, comme n’importe quel contenu issu d’un shortcode. Il reste parfaitement valide sur le plan du balisage HTML. Vue n’a pas pour rôle de créer cette structure, mais simplement de la faire évoluer à partir des données qu’il reçoit. Chaque couche conserve ainsi son rôle : WordPress fournit le cadre et les données, Vue se charge de l’affichage et des interactions. Cette séparation claire permet d’enrichir progressivement l’interface, sans jamais remettre en cause l’API REST ni la structure du plugin, pour l’instant seules les titres sont pris en compte.

Exploiter les données côté interface
Maintenant que Vue est relié à l’API REST et que les données peuvent circuler correctement entre WordPress et l’interface, nous allons pouvoir passer d’un affichage minimal à un affichage un peu plus complet, en utilisant cette fois ci, l’ensemble des données fournies par l’API. Il faut notre que pour illustrer ces deux articles, nous sommes volontairement partis de données relativement classiques : un identifiant, un titre, une année, un lien et une liste de catégories associées à chacun des articles. Rien de plus, mais libre à chacun de composer son endpoint selon ses besoins, l’image d’illustration, les mots clés, l’auteur, le type de post, le statut, l’excerpt…
$articles[] = [
'id' => get_the_ID(),
'title' => get_the_title(),
'date' => get_the_date('Y'),
'permalink' => get_permalink(),
'categories' => wp_get_post_categories(get_the_ID(), ['fields' => 'names']),
];L’intérêt de Vue apparaît précisément à ce moment‑là. Une fois les données disponibles dans l’application, il suffit simplement d’adapter le balisage HTML pour décider ce que l’on affiche et comment on l’affiche. Le shortcode joue ici un rôle simple : fournir une structure lisible, que Vue va enrichir à partir des données injectées.
Dans cet extrait, le shortcode génère la structure du tableau, puis Vue se charge de la remplir à partir de la variable articles. Pour chaque article, nous affichons trois informations : l’année, le titre, puis la liste des catégories associées. La première boucle v-for parcourt les articles et crée une ligne <tr> par élément. À l’intérieur, une seconde boucle parcourt article.categories. C’est ici que Vue devient très confortable : dès qu’un article possède plusieurs catégories, nous les affichons à la suite, sans condition compliquée. Un simple contrôle sur l’index suffit alors à gérer la ponctuation, en insérant une virgule entre les catégories lorsque c’est nécessaire, sans en ajouter après la dernière.
add_shortcode('pem_articles_table', function () {
return '
<div id="pem-articles-table">
<table class="pem-articles-table">
<thead>
<tr>
<th>Année</th>
<th>Titre</th>
<th>Catégories</th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{ article.date }}</td>
<td>
<a :href="article.permalink">{{ article.title }}</a>
</td>
<td>
<span v-for="(cat, index) in article.categories" :key="index">
{{ cat }}<span v-if="index < article.categories.length - 1">, </span>
</span>
</td>
</tr>
</tbody>
</table>
</div>';
});
À ce stade, il n’y a encore ni filtres, ni tris, ni interactions avancées. Le tableau se contente juste d’afficher les données telles qu’elles sont fournies par l’API. Cette étape permet surtout de valider une idée clé : une fois le lien entre Vue et l’API établi, faire évoluer l’affichage devient essentiellement une question de structure et de logique côté interface.
Si de nouveaux besoins apparaissent, deux leviers restent disponibles. Il est possible d’adapter l’interface pour exploiter différemment les données existantes, ou d’enrichir le endpoint pour exposer de nouveaux champs.
Filtres, recherche et interactions naturelles
L’essentiel est en place, nous disposons désormais d’une interface fonctionnelle, lisible, et surtout prête à évoluer. L’objectif de ce chapitre n’est pas de proposer une interface exhaustive ou parfaitement aboutie. Il s’agit plutôt de montrer comment des améliorations parfois perçues comme complexes deviennent simples à mettre en œuvre, dès lors que l’on raisonne à partir des données et que toute la logique d’interface reste concentrée dans Vue.js.
Nous allons donc introduire quelques interactions courantes, représentatives des bases de mise en place :
- une recherche par titre, comme premier point d’entrée côté interface,
- un filtrage par date, pour illustrer la prise en compte d’un critère temporel,
- la possibilité de combiner ces filtres, afin de montrer comment Vue permet d’orchestrer simplement plusieurs conditions.
Ces exemples permettent surtout de poser un cadre de travail lisible : une fois les données disponibles, l’interface peut évoluer progressivement, soit en enrichissant l’affichage, soit en exploitant différemment les informations existantes. Et si de nouveaux besoins apparaissent, rien n’empêche alors de faire évoluer le endpoint pour exposer des données supplémentaires.
Ajouter une recherche sans toucher aux données
Une fois l’affichage de base en place, nous pouvons introduire une première interaction simple : une recherche sur le titre des articles. L’idée n’est pas d’ajouter de la complexité, mais de montrer comment une amélioration fonctionnelle peut s’intégrer naturellement à l’interface, sans modifier ni l’API ni les données d’origine. Concrètement, cela commence par l’ajout d’une nouvelle variable search dans le fichier table.js. Elle va servir uniquement à stocker la valeur saisie par l’utilisateur. Elle n’altère en rien les données existantes ; elle agira simplement sur la manière dont elles seront affichées. Cette logique s’appuie directement sur le rôle de data() dans Vue.js, que nous avons détaillé dans l’article Comprendre data() dans Vue.js.
data() {
return {
articles: [],
search: ''
};
}Pour transformer ces données en affichage, Vue propose un mécanisme central : les propriétés calculées. Une propriété calculée est une valeur dérivée des données existantes, définie sous forme de fonction, et automatiquement recalculée dès que l’une de ses dépendances change. Concrètement, cela permet de préparer les données exactement sous la forme attendue par l’interface, sans alourdir le template ni multiplier les traitements dispersés. Vue se charge d’observer les variations et de maintenir l’affichage synchronisé. L’article Comprendre computed dans Vue.js approfondit ensuite ce principe et montre comment l’exploiter dans des cas plus avancés.
Dans un premier temps, la logique reste volontairement simple. Tant qu’aucun terme n’est saisi, l’interface affiche l’ensemble des articles. Dès qu’une valeur est renseignée, Vue applique automatiquement le filtrage en s’appuyant sur le titre, sans que nous ayons à intervenir davantage.
computed: {
filteredArticles() {
// En l’absence de recherche, on retourne simplement la liste complète
if (!this.search) {
return this.articles;
}
// Sinon, on filtre les articles en fonction du texte saisi
return this.articles.filter(article =>
article.title.toLowerCase().includes(this.search.toLowerCase())
);
}
}Pour rendre cette recherche visible côté interface, le shortcode doit lui aussi évoluer. La première étape consiste à ajouter un champ de recherche dans le balisage HTML. Ce champ est relié à une variable de l’application Vue grâce à la directive v-model, qui établit une liaison automatique entre la valeur saisie par l’utilisateur et l’état interne de l’application. À chaque frappe, la valeur est mise à jour sans qu’il soit nécessaire d’écrire du code supplémentaire.
Une fois ce lien en place, il suffit d’indiquer que le tableau ne s’appuie plus sur la liste complète des articles, mais sur la propriété calculée filteredArticles. Concrètement, cela revient à remplacer la source du v-for dans le shortcode par v-for="article in filteredArticles". L’affichage s’adapte alors immédiatement au contenu du champ de recherche, sans logique conditionnelle dans le template.
Cette mécanique illustre bien la complémentarité entre v-model, qui capte l’entrée utilisateur, et les propriétés calculées, qui transforment les données avant affichage. Pour aller plus loin sur le fonctionnement précis de v-model et ses usages courants, Vue.js v-model Directive propose une présentation claire et progressive.
<div id="pem-articles-table">
<input type="search"
v-model="search"
placeholder="Rechercher un article…" />
<!-- autres éléments -->
<tbody>
<tr v-for="article in filteredArticles" :key="article.id">
<td>{{ article.date }}</td>
<td>
<a :href="article.permalink">{{ article.title }}</a>
</td>
<td>
<span v-for="(cat, index) in article.categories" :key="index">
{{ cat }}<span v-if="index < article.categories.length - 1">, </span>
</span>
</td>
</tr>
</tbody>
<!-- autres éléments du shortcode -->
</divÀ ce stade, rien de plus n’est requis. La recherche s’effectue entièrement côté interface, sans appel supplémentaire au serveur. Les données ne sont ni modifiées ni dupliquées ; l’interface propose simplement une autre manière de parcourir le même jeu d’articles. Cette première étape suffit à poser les bases. Une fois cette logique en place, l’ajout de nouvelles interactions devient naturel, progressif et lisible, sans remettre en cause l’existant.
Filtrer par date : recherche textuelle et interaction directe
Ce raisonnement ne se limite pas à une seule colonne. Une recherche initialement posée sur le titre peut naturellement s’étendre à d’autres propriétés, comme l’année de publication. Le champ search devient alors un point d’entrée transversal : une seule saisie permet d’interroger plusieurs champs à la fois, simplement en enrichissant la propriété calculée filteredArticles. La logique reste centralisée, lisible, et évolutive.
Cette approche fonctionne très bien pour une recherche textuelle libre. Mais lorsqu’on travaille avec des données récurrentes ou structurantes, comme les dates, les catégories, les mots clés ou les auteurs, une autre forme d’interaction devient pertinente. Plutôt que de demander à l’utilisateur de retaper une information déjà visible, on peut lui proposer de cliquer directement sur cette donnée pour activer un filtre ciblé. La recherche textuelle et les filtres par clic ne s’opposent pas : ils se complètent.
C’est précisément ici que Vue montre son intérêt. Toute la logique de filtrage reste regroupée dans une seule propriété calculée, qui orchestre à la fois la recherche globale et les filtres ciblés. Dans table.js, on commence par mémoriser l’année sélectionnée, puis on fait évoluer filteredArticles pour combiner les critères, sans multiplier les conditions éparpillées dans le code.
data() {
return {
articles: [],
search: '',
activeYear: null
};
},
computed: {
filteredArticles() {
const search = this.search.toLowerCase();
return this.articles.filter(article => {
// Recherche textuelle transversale (titre + année)
const matchSearch =
!search ||
article.title.toLowerCase().includes(search) ||
String(article.date).includes(search);
// Filtre ciblé activé par interaction
const matchActiveYear =
!this.activeYear || article.date === this.activeYear;
// Les critères se combinent naturellement
return matchActiveYear && matchSearch;
});
}
}Côté interface, l’interaction reste très simple. Cliquer sur une année active le filtre correspondant, un second clic permet de le désactiver. Le HTML se contente d’exprimer cette intention, tandis que Vue interprète l’état courant et met à jour l’affichage en conséquence.
<td>
<span
@click="activeYear = activeYear === article.date ? null : article.date"
:class="{ 'is-active': activeYear === article.date }">
{{ article.date }}
</span>
</td>
L’essentiel se joue ici sur la lisibilité. Des comportements qui deviendraient rapidement difficiles à maintenir en JavaScript classique restent ici exprimés de manière claire, car toute la logique est centralisée dans filteredArticles. Les filtres peuvent s’additionner sans complexifier l’interface ni le code.
Pour accompagner cette interaction, un simple retour visuel suffit. La classe is-active, appliquée dynamiquement à l’élément sélectionné, permet de rendre le filtrage immédiatement compréhensible pour l’utilisateur. Quelques règles CSS suffisent alors à créer cette mise en surbrillance, sans ajouter de logique supplémentaire.
span.is-active {
font-weight: bold;
text-decoration: underline;
}
Ce type d’ajustement n’altère pas la logique fonctionnelle, mais améliore nettement la lecture de l’interface. La recherche, les filtres et le retour visuel racontent ainsi une même histoire, construite progressivement à partir des données déjà disponibles.

Styliser l’interface sans interférer avec le reste du site
Une fois l’interface fonctionnelle en place, la question du style se pose naturellement. Là encore, l’objectif n’est pas de produire une feuille de styles exhaustive, mais de poser une méthode claire, évolutive et facile à maintenir.
Dans le contexte d’un plugin WordPress, et plus encore lorsqu’une interface est pilotée par Vue.js, il est recommandé de restreindre la portée des styles. La pratique la plus simple consiste à faire précéder l’ensemble des règles par l’identifiant du conteneur principal, celui généré par le shortcode, ici #pem-articles-table. On peut utilement y associer une classe explicite, par exemple .is-vue-interface, afin d’identifier clairement ce bloc comme une interface Vue et de distinguer, si besoin, plusieurs modes d’interfaçage reposant sur une même API REST.
Ce type d’isolation évite les conflits avec le thème actif ou d’autres extensions, tout en rendant les styles plus lisibles et plus simples à faire évoluer. À ce stade, le passage à SCSS devient presque naturel. Sans être obligatoire, il permet de structurer les styles par blocs, de factoriser certaines règles et de préparer plus sereinement les évolutions à venir. Pour celles et ceux qui souhaitent aller plus loin sur l’automatisation et la compilation, Automatiser son flux de travail : compilation SCSS et mise en ligne avec Gulp détaille une approche progressive et concrète. Dans ce contexte, on peut par exemple traduire les règles précédentes en SCSS, en posant quelques bases simples.
// _variables.scss
$spacing-sm: 0.4em;
$spacing-md: 0.6em;
$font-weight-active: 600;
// table.scss
@use 'variables' as *;
#pem-articles-table.is-vue-interface {
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: $spacing-sm $spacing-md;
text-align: left;
}
.is-active {
font-weight: $font-weight-active;
text-decoration: underline;
}
}Cette organisation reste volontairement simple, mais elle ouvre déjà plusieurs possibilités : extraction des variables dans un fichier dédié, découpage par composants, ou encore mutualisation de styles communs via @use ou @forward, Migration SCSS : de @import vers @use avec Dart Sass. Le CSS généré reste parfaitement classique, tandis que le SCSS apporte un cadre plus confortable pour structurer et faire évoluer les styles.
On conserve ainsi une séparation nette entre les responsabilités. Vue.js gère l’état et les interactions, le HTML décrit la structure de l’interface, et les styles se contentent de traduire visuellement ces états. Libre ensuite d’enrichir ou de personnaliser l’apparence, sans jamais remettre en cause ni la logique Vue, ni les données exposées par WordPress. L’essentiel est posé : une base claire, isolée et pleinement maîtrisable.
Conclusion
Dans cet article, nous avons volontairement mis l’accent sur les principes et les mécanismes, plutôt que sur une accumulation de fonctionnalités. L’objectif n’était pas de reproduire une interface complète, mais de poser des bases solides et réutilisables, en clarifiant le rôle de chaque couche.
Cette approche permet de mieux comprendre ce qui se joue réellement : WordPress expose des données cohérentes via l’API REST, Vue.js se charge de leur lecture et de leur mise en forme, et l’interface évolue sans jamais remettre en cause le socle existant. Le code gagne ainsi en clarté, en lisibilité, et surtout en capacité d’évolution.
Libre ensuite d’aller plus loin. Recherche enrichie, filtres avancés, tris multiples, pagination ou enrichissement visuel peuvent être ajoutés progressivement, sans rupture ni dette technique. Les fondations sont en place, et la séparation entre WordPress, l’API et l’interface reste intacte. L’interface mise en ligne sur Puce & Média illustre concrètement cette démarche dans un contexte réel. Elle montre comment ces principes peuvent être appliqués à une interface plus aboutie, tout en conservant la même logique de construction et la même lisibilité du code.
