Essayez sans attendre l'hébergement proposé par WordPress
-15% sur le premier mois avec le code 2025PRESS15AFF

Essayer maintenant

Implémenter les données structurées Schema.org en JSON-LD sur WordPress sans plugin

Un plugin de plus, une requête de base de données en plus, un point de défaillance en plus : pour un simple bout de JSON-LD, ça fait cher payé. Pourtant, Google adore ce format et le récompense en rich snippets, à condition de le générer proprement. Bonne nouvelle : quelques lignes de PHP bien placées dans vos hooks suffisent à faire le boulot, sans alourdir votre site d’un milligramme. C’est justement l’objet de cet article : implémenter des données structurées Schema.org en JSON-LD sur WordPress, sans dépendre d’un plugin tiers.

Comprendre le JSON-LD et les données structurées Schema.org

Le JSON-LD (JSON for Linked Data), c’est un format qui permet d’ajouter des données structurées à une page web sans toucher au HTML visible. Concrètement, on injecte un bloc de code JavaScript (enfin, du JSON, mais dans une balise <script type="application/ld+json">) qui décrit le contenu de la page pour les moteurs de recherche : type de contenu, auteur, date de publication, prix d’un produit, etc.

Google recommande explicitement ce format plutôt que Microdata ou RDFa. Pourquoi ? Parce que le JSON-LD est totalement séparé du HTML. Pas besoin d’ajouter des attributs itemscope ou itemprop partout dans votre code, ce qui rend le HTML illisible et fragile aux modifications de thème. Avec le JSON-LD, on centralise tout dans un seul bloc, on peut le générer dynamiquement en PHP, et si demain on change de thème, le balisage reste intact. C’est net.

Les types de données structurées les plus courants qu’on retrouve sur WordPress : Article (pour les articles de blog), Product (pour le e-commerce), FAQPage (pour les pages de questions fréquentes), BreadcrumbList (pour le fil d’Ariane). Chacun a sa propre structure de propriétés à respecter, définie par le vocabulaire Schema.org.

Pourquoi éviter les plugins pour le balisage structuré

Bon, soyons honnêtes : les plugins comme Yoast SEO ou Rank Math font du bon boulot pour générer des données structurées basiques. Mais dès qu’on a des besoins spécifiques (un type de contenu personnalisé, une structure imbriquée particulière), on se retrouve vite bloqué par les limitations de l’interface.

Développer son propre balisage JSON-LD en PHP, directement dans le thème ou un plugin maison, présente plusieurs avantages concrets :

  • Performance : pas de requêtes supplémentaires en base de données, pas de code JavaScript ou CSS additionnel chargé inutilement.
  • Légèreté : votre code ne génère que ce dont vous avez besoin, rien de superflu.
  • Contrôle total : vous décidez exactement quelles propriétés afficher, sans dépendre des choix de configuration d’un plugin tiers.
  • Pas de dépendance externe : un plugin peut être abandonné, mal maintenu ou entrer en conflit avec un autre. Votre code à vous, vous le maîtrisez de A à Z.

Pour un développeur à l’aise avec les hooks WordPress, c’est une solution qui tient en quelques dizaines de lignes de PHP. Et niveau maintenance, franchement, c’est plus simple à déboguer qu’une configuration de plugin tentaculaire.

Le fonctionnement des hooks wp_head et template_redirect

Pour injecter du JSON-LD dans WordPress sans plugin, deux hooks reviennent systématiquement : wp_head et template_redirect.

Le hook wp_head est le plus simple : il s’exécute juste avant la fermeture de la balise </head> et permet d’injecter directement du contenu, comme notre script JSON-LD. C’est suffisant si vous voulez afficher le même type de balisage partout, ou sur un type de page unique (les articles, par exemple).

function dev_wp_ajouter_jsonld() {
    $schema = array(
        '@context' => 'https://schema.org',
        '@type' => 'Article',
        'headline' => get_the_title(),
        'datePublished' => get_the_date('c'),
        'author' => array(
            '@type' => 'Person',
            'name' => get_the_author()
        )
    );
    echo '<script type="application/ld+json">' . wp_json_encode($schema) . '</script>';
}
add_action('wp_head', 'dev_wp_ajouter_jsonld');

Le hook template_redirect, lui, s’exécute plus tôt dans le cycle de chargement, avant que WordPress ne détermine le template final à afficher. Il offre plus de flexibilité pour conditionner l’affichage selon le contexte : is_single(), is_product() (avec WooCommerce), is_page('faq'), etc. On peut ainsi générer un balisage Product uniquement sur les pages produits, un FAQPage sur les pages dédiées, et un BreadcrumbList partout ailleurs. C’est la méthode à privilégier dès que votre site gère plusieurs types de contenus avec des schémas différents.

Générer dynamiquement le JSON-LD selon le type de contenu

Maintenant qu’on a posé les bases (hooks, avantages du sans-plugin), passons au concret. L’idée, c’est de créer un système qui s’adapte automatiquement au type de contenu affiché : un article n’a pas besoin du même balisage qu’une fiche produit ou qu’une page FAQ. On va donc construire, étape par étape, les blocs PHP nécessaires pour chaque cas, avant de tout centraliser dans une fonction unique.

Balisage pour les articles de blog (schema Article)

Pour un article de blog, WordPress fournit déjà tout ce dont on a besoin via ses fonctions natives. Pas besoin d’aller chercher plus loin :

function dev_wp_schema_article() {
    $schema = array(
        '@context' => 'https://schema.org',
        '@type' => 'Article',
        'headline' => get_the_title(),
        'datePublished' => get_the_date('c'),
        'dateModified' => get_the_modified_date('c'),
        'author' => array(
            '@type' => 'Person',
            'name' => get_the_author_meta('display_name')),
        'image' => get_the_post_thumbnail_url(get_the_ID(), 'full'));

    return $schema;
}

Le format 'c' pour les dates est important : c’est le format ISO 8601, celui attendu par Schema.org. Un détail qui paraît anodin, mais qui évite pas mal d’erreurs dans le Rich Results Test de Google.

Balisage pour les fiches produit (schema Product)

Sur une boutique WooCommerce, les infos produit (prix, disponibilité, SKU) sont stockées dans les métadonnées du post. On peut y accéder directement avec get_post_meta(), mais je préfère utiliser les méthodes de l’objet WC_Product : c’est plus lisible et ça évite de se tromper sur les clés meta.

function dev_wp_schema_product() {
    global $post;
    $product = wc_get_product($post->ID);

    if (!$product) {
        return array();
    }

    return array(
        '@context' => 'https://schema.org',
        '@type' => 'Product',
        'name' => $product->get_name(),
        'sku' => $product->get_sku(),
        'image' => wp_get_attachment_url($product->get_image_id()),
        'offers' => array(
            '@type' => 'Offer',
            'priceCurrency' => get_woocommerce_currency(),
            'price' => $product->get_price(),
            'availability' => $product->is_in_stock()
                ? 'https://schema.org/InStock'
                : 'https://schema.org/OutOfStock'));
}

Attention néanmoins : si le produit a des variations, il faudra adapter le prix affiché (min/max) et éventuellement boucler sur chaque variation pour un balisage plus précis. On reste ici sur le cas simple, mais gardez ça en tête si votre catalogue est complexe.

Balisage pour les FAQ (schema FAQPage)

Pour une page FAQ, on va boucler sur des champs ACF (Advanced Custom Fields) de type repeater, ou sur des blocs Gutenberg personnalisés si vous n’utilisez pas ACF. L’idée reste la même : construire un tableau mainEntity avec autant d’entrées Question/Answer que nécessaire.

function dev_wp_schema_faq() {
    $faqs = get_field('faq_items');

    if (!$faqs) {
        return array();
    }

    $main_entity = array();

    foreach ($faqs as $faq) {
        $main_entity[] = array(
            '@type' => 'Question',
            'name' => $faq['question'],
            'acceptedAnswer' => array(
                '@type' => 'Answer',
                'text' => $faq['reponse']));
    }

    return array(
        '@context' => 'https://schema.org',
        '@type' => 'FAQPage',
        'mainEntity' => $main_entity);
}

Si vous n’avez pas ACF, on peut faire pareil avec get_blocks() et parse_blocks(get_the_content()) pour extraire le contenu d’un bloc Gutenberg custom. Un peu plus verbeux, mais tout aussi efficace.

Créer une fonction PHP réutilisable et modulaire

Voilà la partie la plus importante à mes yeux : centraliser toute cette logique dans une seule fonction générique. Ça évite de dupliquer du code et ça facilite grandement la maintenance (si demain Google change un attribut requis, on modifie à un seul endroit).

function dev_wp_generate_schema() {
    $schema = array();

    if (is_singular('post')) {
        $schema = dev_wp_schema_article();
    } elseif (function_exists('is_product') && is_product()) {
        $schema = dev_wp_schema_product();
    } elseif (is_page_template('template-faq.php')) {
        $schema = dev_wp_schema_faq();
    }

    if (empty($schema)) {
        return;
    }

    echo '<script type="application/ld+json">' . wp_json_encode($schema) . '</script>' . "\n";
}
add_action('wp_head', 'dev_wp_generate_schema');

Je vous conseille de placer tout ce code dans un fichier dédié, schema-json-ld.php par exemple, plutôt que de tout entasser dans functions.php (qui devient vite illisible sur les gros projets). Ensuite, un simple require_once get_template_directory() . '/inc/schema-json-ld.php'; dans votre functions.php suffit à tout charger proprement.

Cette architecture modulaire a un avantage énorme : ajouter un nouveau type de contenu (un événement, une recette de cuisine, une critique) revient juste à créer une nouvelle fonction dev_wp_schema_xxx() et à l’ajouter dans le switch. Le reste du système ne bouge pas. C’est exactement ce qu’on recherche en développement WordPress : du code découplé, facile à faire évoluer sans tout casser.

Valider et déboguer son balisage JSON-LD

Une fois le code injecté dans wp_head, encore faut-il vérifier qu’il fonctionne correctement. Google n’affiche pas systématiquement les rich results, mais si votre balisage contient des erreurs, il ne les affichera jamais. Voici comment s’assurer que tout est propre, avec les bons outils et les bons réflexes.

Première étape (et la plus simple) : le Rich Results Test de Google. Rendez-vous sur search.google.com/test/rich-results et collez soit l’URL de votre page, soit directement le code source (pratique pour tester en local ou sur un environnement de staging). Lancez le test : Google va crawler la page et extraire tous les blocs JSON-LD détectés. Résultat affiché en quelques secondes : soit « Résultat valide » avec le nombre d’éléments enrichis détectés (Article, Product, FAQ, etc.), soit une liste d’erreurs et d’avertissements. Les erreurs bloquent l’éligibilité au rich result, les avertissements sont des recommandations (propriétés optionnelles manquantes par exemple). Cliquez sur chaque item pour voir précisément quelle propriété pose problème.

Le souci, c’est que cet outil ne valide que les types Schema.org supportés par les rich results Google (donc un sous-ensemble limité). Si vous utilisez des types moins courants (Event, Organization, BreadcrumbList sans affichage enrichi), tournez-vous vers Schema Markup Validator (validator.schema.org). Cet outil, maintenu par Schema.org lui-même, valide n’importe quel type de vocabulaire, sans filtrage lié aux fonctionnalités Google. C’est l’outil de référence pour une validation exhaustive et neutre.

Côté debug, quelques erreurs reviennent souvent et valent le coup d’œil avant même de lancer un test :

  • Guillemets mal échappés : si vous injectez des données utilisateur (titre d’article, description) contenant des guillemets doubles, ça casse le JSON. Utilisez toujours wp_json_encode() plutôt que de construire la chaîne à la main, il échappe automatiquement les caractères problématiques.
  • Dates au mauvais format : Schema.org exige le format ISO 8601 (2024-03-15T10:30:00+01:00). Un get_the_date() sans formatage adapté renvoie souvent une date en français, illisible pour Google.
  • Propriétés requises manquantes : chaque type a ses champs obligatoires (name, image, datePublished pour un Article par exemple). Vérifiez la documentation officielle sur schema.org avant de coder.

Dernière astuce, mais pas des moindres : faites un clic droit puis « Afficher le code source de la page » (view-source:). Cherchez application/ld+json dans le code, ça confirme que le script est bien injecté et vous permet de repérer visuellement une erreur de syntaxe (accolade manquante, virgule en trop). Et pensez à tester en navigation privée : le cache navigateur ou un plugin de cache serveur peut afficher une ancienne version du balisage, ce qui fausse complètement le diagnostic.