Comment créer un thème WordPress personnalisé

Par Xavier Deloffre

Créer un thème WordPress personnalisé, ce n’est pas simplement écrire quelques fichiers PHP et ajouter une feuille CSS. C’est construire une couche de présentation complète, capable de dialoguer proprement avec le cœur de WordPress, l’éditeur de blocs, les extensions, les hooks, les modèles de données, les performances front-end et les contraintes de maintenance d’un vrai site en production.
Bien comprendre l’architecture d’un thème wordpress personnalisé Un thème WordPress personnalisé est l’ensemble des fichiers qui contrôlent l’apparence, la structure HTML, le chargement des ressources front-end et une partie du comportement d’un site ; Il s’agit ici d’un thème original qui impacte notamment le prix d’un site Internet sur mesure lors que vous faites appel à des professionnels.Et il ne s’agit pas seulement d’un habillage graphique : Un thème bien conçu organise les templates, déclare les fonctionnalités prises en charge, charge les fichiers CSS et JavaScript proprement, respecte la hiérarchie de WordPress et reste compatible avec l’éditeur de blocs, les extensions et les futures évolutions du site.

L’architecture d’un thème WordPress personnalisé avant de commencer à  le créer

Avant d’écrire la première ligne de code, il faut distinguer deux grandes familles de thèmes : les thèmes classiques et les thèmes blocs. Un thème classique repose principalement sur des fichiers PHP comme index.php, single.php, page.php, archive.php, header.php, footer.php ou encore functions.php. Il utilise la hiérarchie des templates WordPress pour déterminer quel fichier charger selon le contexte : page statique, article, archive, catégorie, résultat de recherche, erreur 404 ou type de contenu personnalisé. Un thème bloc, lui, s’inscrit dans la logique du Full Site Editing. Au lieu de s’appuyer principalement sur des templates PHP, il utilise des fichiers HTML placés dans des dossiers comme templates et parts, ainsi qu’un fichier central theme.json. Ce dernier permet de déclarer les réglages globaux du thème : couleurs, typographies, espacements, tailles de police, styles de blocs, largeurs de contenu et options disponibles dans l’éditeur. Cette approche rend une grande partie de la structure modifiable depuis l’interface d’administration WordPress.

Dans un projet professionnel, le choix entre thème WP classique et thème bloc dépend du niveau de contrôle souhaité, du profil des administrateurs, du degré de personnalisation attendu et de la stratégie de maintenance. Un thème classique convient encore très bien aux sites fortement sur mesure, avec beaucoup de logique PHP, de champs personnalisés, de types de contenus spécifiques ou d’intégrations métier. Un thème bloc est intéressant lorsque l’on veut donner plus d’autonomie éditoriale aux équipes, standardiser les composants visuels et exploiter pleinement l’éditeur de site. Dans les deux cas, WordPress attend une structure minimale. Un thème classique doit contenir au minimum un fichier style.css avec l’en-tête du thème et un fichier index.php. Un thème bloc doit contenir un fichier style.css et un fichier templates/index.html. Sans cette base, WordPress ne peut pas reconnaître correctement le thème ni l’afficher dans l’administration. Pour un thème classique minimal, l’arborescence de départ peut ressembler à ceci :

mon-theme/
├── style.css
├── index.php
├── functions.php
├── header.php
├── footer.php
├── single.php
├── page.php
├── archive.php
├── search.php
├── 404.php
├── screenshot.png
└── assets/
    ├── css/
    ├── js/
    ├── img/
    └── fonts/

Cette structure peut ensuite être enrichie selon les besoins du projet. Sur un site plus avancé, on ajoutera souvent des dossiers pour les composants réutilisables, les classes PHP, les modèles partiels, les traductions, les fichiers compilés ou les sources front-end.

mon-theme/
├── assets/
│   ├── css/
│   ├── js/
│   ├── img/
│   └── fonts/
├── inc/
│   ├── setup.php
│   ├── enqueue.php
│   ├── template-functions.php
│   ├── custom-post-types.php
│   └── security.php
├── languages/
├── template-parts/
│   ├── content.php
│   ├── content-page.php
│   ├── content-single.php
│   └── content-none.php
├── style.css
├── functions.php
├── index.php
├── header.php
├── footer.php
├── single.php
├── page.php
├── archive.php
├── search.php
├── 404.php
└── screenshot.png

Le dossier assets contient les fichiers destinés au navigateur : feuilles de style, scripts JavaScript, images statiques et polices. Le dossier inc permet de découper le fichier functions.php en plusieurs fichiers spécialisés. Le dossier template-parts contient des fragments d’affichage réutilisables afin d’éviter de dupliquer du HTML dans plusieurs templates. Le fichier style.css ne sert pas uniquement à écrire du CSS. Il contient aussi l’en-tête déclaratif du thème, utilisé par WordPress pour identifier le nom, l’auteur, la version, le domaine de traduction et les compatibilités. Cet en-tête doit être placé tout en haut du fichier.

/*
Theme Name: Mon thème personnalisé
Theme URI: https://exemple.com
Author: Votre nom
Author URI: https://exemple.com
Description: Thème WordPress personnalisé optimisé pour la performance.
Version: 1.0.0
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 8.1
Text Domain: mon-theme
*/

Le champ Theme Name correspond au nom affiché dans l’administration. Le champ Version permet de suivre les évolutions du thème, mais il peut aussi servir au cache busting lorsque cette valeur est utilisée pour versionner les fichiers CSS et JavaScript. Le champ Text Domain est indispensable pour l’internationalisation : il doit être cohérent avec les fonctions de traduction utilisées dans le thème, comme __(), _e(), esc_html__() ou esc_html_e(). Le fichier index.php est le modèle de secours. Si WordPress ne trouve pas de template plus spécifique dans la hiérarchie des templates, il charge ce fichier. Il doit donc être suffisamment générique pour afficher une liste de contenus dans des conditions variées.

<?php get_header(); ?>
<main id="primary" class="site-main">
    <?php if ( have_posts() ) : ?>
        <?php while ( have_posts() ) : the_post(); ?>
            <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                <h2>
                    <a href="<?php the_permalink(); ?>">
                        <?php the_title(); ?>
                    </a>
                </h2>
                <div class="entry-content">
                    <?php the_excerpt(); ?>
                </div>
            </article>
        <?php endwhile; ?>
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p><?php esc_html_e( 'Aucun contenu trouvé.', 'mon-theme' ); ?></p>
    <?php endif; ?>
</main>
<?php get_footer(); ?>

Ce code contient la boucle WordPress, appelée couramment The Loop. Elle repose sur les fonctions have_posts() et the_post(). La première vérifie s’il existe des contenus à afficher, la seconde prépare les données du contenu courant. À l’intérieur de cette boucle, on peut utiliser des fonctions comme the_title(), the_permalink(), the_excerpt(), the_content(), the_post_thumbnail() ou get_the_date(). Le fichier functions.php agit comme le point d’entrée technique du thème. Il est chargé automatiquement par WordPress lorsque le thème est actif. On y déclare les supports WordPress, les menus, les tailles d’images, les scripts, les feuilles de style, les hooks et certains comportements spécifiques.

<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
function mon_theme_setup() {
    load_theme_textdomain( 'mon-theme', get_template_directory() . '/languages' );
    add_theme_support( 'title-tag' );
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' ) );
    add_theme_support( 'custom-logo' );
    add_theme_support( 'responsive-embeds' );
    register_nav_menus( array(
        'primary' => __( 'Menu principal', 'mon-theme' ),
        'footer'  => __( 'Menu pied de page', 'mon-theme' ),
    ) );
}
add_action( 'after_setup_theme', 'mon_theme_setup' );
function mon_theme_assets() {
    wp_enqueue_style(
        'mon-theme-style',
        get_stylesheet_uri(),
        array(),
        wp_get_theme()->get( 'Version' )
    );
    wp_enqueue_script(
        'mon-theme-main',
        get_template_directory_uri() . '/assets/js/main.js',
        array(),
        wp_get_theme()->get( 'Version' ),
        true
    );
}
add_action( 'wp_enqueue_scripts', 'mon_theme_assets' );

La condition if ( ! defined( 'ABSPATH' ) ) empêche l’accès direct au fichier PHP depuis le navigateur. Ce n’est pas une protection complète contre toutes les attaques, mais c’est une bonne pratique simple à appliquer dans les fichiers du thème qui contiennent de la logique PHP.

La fonction add_theme_support() indique à WordPress les fonctionnalités gérées par le thème. Avec title-tag, WordPress peut gérer dynamiquement la balise <title>. Avec post-thumbnails, le thème active les images mises en avant. Avec custom-logo, il permet à l’administrateur de configurer un logo depuis l’interface. Avec responsive-embeds, les contenus embarqués s’adaptent mieux aux écrans mobiles.

Le chargement des fichiers CSS et JavaScript ne doit pas se faire en dur dans header.php. Il faut utiliser wp_enqueue_style() et wp_enqueue_script(). Cela permet à WordPress de gérer les dépendances, l’ordre de chargement, les versions, les conflits avec les extensions et l’optimisation globale des ressources.

Pour aller plus loin, on peut charger certains scripts uniquement dans des contextes précis. Par exemple, inutile de charger un script de galerie sur toutes les pages si seules les pages utilisant un modèle particulier en ont besoin.

function mon_theme_conditional_assets() {
    if ( is_page_template( 'templates/page-gallery.php' ) ) {
        wp_enqueue_script(
            'mon-theme-gallery',
            get_template_directory_uri() . '/assets/js/gallery.js',
            array(),
            wp_get_theme()->get( 'Version' ),
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'mon_theme_conditional_assets' );

Les fichiers header.php et footer.php contiennent respectivement le début et la fin de la structure HTML globale. Le fichier header.php inclut généralement le doctype, la balise <html>, la balise <head>, l’appel à wp_head(), l’ouverture du <body> et le haut de page. Le fichier footer.php contient le pied de page, l’appel à wp_footer() et la fermeture des balises HTML principales.

<!doctype html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<header class="site-header">
    <div class="site-branding">
        <?php
        if ( has_custom_logo() ) {
            the_custom_logo();
        } else {
            echo '<p class="site-title"><a href="' . esc_url( home_url( '/' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . '</a></p>';
        }
        ?>
    </div>
    <nav class="site-navigation" aria-label="<?php esc_attr_e( 'Navigation principale', 'mon-theme' ); ?>">
        <?php
        wp_nav_menu( array(
            'theme_location' => 'primary',
            'menu_id'        => 'primary-menu',
        ) );
        ?>
    </nav>
</header>

L’appel à wp_head() est indispensable. Il permet à WordPress, au thème et aux extensions d’injecter des métadonnées, des styles, des scripts ou des éléments nécessaires au fonctionnement du site. De la même manière, wp_footer() doit être présent juste avant la fermeture de </body>. Sans ces deux fonctions, certaines extensions, scripts analytiques, optimisations ou fonctionnalités de l’éditeur peuvent ne pas fonctionner correctement. L’architecture doit aussi tenir compte de la hiérarchie des templates. Par exemple, pour afficher un article standard, WordPress peut chercher single-post.php, puis single.php, puis singular.php, puis index.php. Pour une page, il peut chercher un template personnalisé, puis page-{slug}.php, page-{id}.php, page.php, singular.php et enfin index.php. Cette logique évite de créer une architecture rigide : chaque fichier répond à un contexte précis. Dans un thème robuste, il est préférable de ne pas mettre tout le HTML dans les fichiers principaux. Les fichiers template-parts permettent de factoriser l’affichage d’un article, d’une carte, d’un en-tête de page, d’un bloc d’appel à l’action ou d’une zone de métadonnées.

<?php get_template_part( 'template-parts/content', get_post_type() ); ?>

Avec cet appel, WordPress peut charger automatiquement un fichier comme template-parts/content-post.php pour les articles ou template-parts/content-page.php pour les pages, si ces fichiers existent. Sinon, il peut utiliser un fichier plus générique comme template-parts/content.php.

Un thème personnalisé doit également être pensé pour l’internationalisation. Même si le site est uniquement en français au lancement, il est préférable d’utiliser un text domain dès le départ. Cela facilite une traduction future et respecte les standards WordPress.

<h1><?php esc_html_e( 'Bienvenue sur notre site', 'mon-theme' ); ?></h1>

La sécurité fait aussi partie de l’architecture. Les données affichées doivent être échappées selon leur contexte. Pour du texte simple, on utilise esc_html(). Pour une URL, esc_url(). Pour un attribut HTML, esc_attr(). Pour du contenu HTML autorisé, wp_kses_post(). Cette discipline doit être présente dans tous les templates, même lorsque les données semblent provenir d’une source fiable.

Enfin, l’architecture doit anticiper l’évolution du thème. Un thème personnalisé peut commencer avec quelques fichiers, mais devenir rapidement complexe si le site ajoute un blog, un espace ressources, des landing pages, des types de contenus personnalisés, des champs ACF, un méga-menu, un système de composants ou une logique multilingue. Mieux vaut donc séparer les responsabilités dès le départ : les templates pour l’affichage, functions.php pour le chargement initial, inc pour la logique PHP, assets pour les ressources front-end et template-parts pour les composants visuels réutilisables. Une bonne architecture de thème WordPress personnalisé doit donc répondre à trois objectifs : Etre lisible pour les développeurs, flexible pour les administrateurs et stable pour la production. C’est cette base qui permet ensuite d’ajouter des fonctionnalités plus avancées, comme des blocs personnalisés, une intégration avec l’API REST, un système de build front-end, des optimisations de performance ou une assistance au développement avec des outils IA comme Claude Code.

architecture theme wordpress personnalise

Développer les templates, les hooks et la logique métier du thème wp personnalisé

Un thème WordPress personnalisé sérieux ne doit pas être pensé comme une simple collection de fichiers PHP. Il doit être conçu comme une architecture modulaire, dans laquelle chaque template répond à un contexte d’affichage précis, chaque hook intervient au bon moment dans le cycle de chargement WordPress et chaque portion de logique métier reste maintenable. C’est cette séparation entre affichage, comportement et données qui permet de créer un thème stable, évolutif et adapté à un vrai site professionnel. La première règle consiste à respecter la hiérarchie des templates WordPress. Cette hiérarchie détermine automatiquement le fichier à charger selon la page consultée. Pour une page statique, WordPress peut chercher un modèle personnalisé, puis page-{slug}.php, page-{id}.php, page.php, singular.php, puis index.php. Pour un article, il peut charger single-post.php, single.php, singular.php, puis index.php. Pour une archive de catégorie, il peut chercher category-{slug}.php, category-{id}.php, category.php, archive.php, puis index.php. Cette logique permet de créer des gabarits très précis sans dupliquer tout le thème. Par exemple, un blog peut utiliser single.php pour les articles classiques, tandis qu’un type de contenu personnalisé nommé projet peut utiliser single-projet.php. De la même manière, une archive standard peut être gérée par archive.php, alors qu’une archive dédiée aux réalisations peut disposer de son propre fichier archive-projet.php.

Dans single.php, on peut isoler l’affichage d’un article avec une structure claire :

<?php get_header(); ?>
<main id="primary" class="site-main">
    <?php while ( have_posts() ) : the_post(); ?>
        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <header class="entry-header">
                <?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
                <p class="entry-meta">
                    <?php echo esc_html( get_the_date() ); ?>
                </p>
            </header>
            <div class="entry-content">
                <?php the_content(); ?>
            </div>
        </article>
        <?php comments_template(); ?>
    <?php endwhile; ?>
</main>
<?php get_footer(); ?>

Ce template reste volontairement simple. Il récupère l’en-tête du site avec get_header(), ouvre la zone principale, exécute la boucle WordPress, affiche le titre, la date, le contenu, puis charge les commentaires. Enfin, il appelle get_footer(). Cette organisation respecte le fonctionnement natif de WordPress et facilite la maintenance. En revanche, sur un thème plus complet, il est préférable de ne pas laisser toute la structure HTML directement dans single.php. Le fichier principal peut seulement orchestrer l’affichage et déléguer le rendu à un fichier partiel. Cela évite de créer des templates longs, difficiles à lire et pénibles à faire évoluer.

<?php get_header(); ?>
<main id="primary" class="site-main">
    <?php
    while ( have_posts() ) :
        the_post();
        get_template_part( 'template-parts/content', 'single' );
        if ( comments_open() || get_comments_number() ) {
            comments_template();
        }
    endwhile;
    ?>
</main>
<?php get_footer(); ?>

Dans cette version, single.php devient un contrôleur de rendu très lisible. Le fichier template-parts/content-single.php contient le HTML détaillé de l’article. Cette séparation rend le thème plus propre, surtout lorsque plusieurs templates partagent des composants communs. Pour éviter un thème monolithique, on utilise souvent des fichiers partiels dans un dossier template-parts :

mon-theme/
└── template-parts/
    ├── content.php
    ├── content-page.php
    ├── content-single.php
    ├── content-card.php
    ├── content-none.php
    ├── hero.php
    ├── breadcrumbs.php
    └── pagination.php

Ensuite, on les appelle ainsi :

get_template_part( 'template-parts/content', get_post_type() );

La fonction get_template_part() permet de charger dynamiquement un fragment de template. Si le type de contenu courant est post, WordPress cherchera par exemple template-parts/content-post.php, puis template-parts/content.php. Cela permet d’avoir un rendu spécifique pour certains types de contenus tout en conservant un fallback générique. Pour une carte d’article réutilisable dans une archive, une page d’accueil ou une section de contenus liés, on peut créer un fichier template-parts/content-card.php :

<article id="post-<?php the_ID(); ?>" <?php post_class( 'post-card' ); ?>>
    <?php if ( has_post_thumbnail() ) : ?>
        <a href="<?php the_permalink(); ?>" class="post-card__image">
            <?php the_post_thumbnail( 'medium_large' ); ?>
        </a>
    <?php endif; ?>
    <div class="post-card__body">
        <h2 class="post-card__title">
            <a href="<?php the_permalink(); ?>">
                <?php the_title(); ?>
            </a>
        </h2>
        <p class="post-card__excerpt">
            <?php echo esc_html( wp_trim_words( get_the_excerpt(), 24 ) ); ?>
        </p>
    </div>
</article>

Ce composant peut ensuite être appelé dans archive.php :

<?php get_header(); ?>

<main id="primary" class="site-main">
    <?php if ( have_posts() ) : ?>
        <div class="post-grid">
            <?php
            while ( have_posts() ) :
                the_post();
                get_template_part( 'template-parts/content', 'card' );
            endwhile;
            ?>
        </div>
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <?php get_template_part( 'template-parts/content', 'none' ); ?>
    <?php endif; ?>
</main>
<?php get_footer(); ?>

Les hooks sont l’autre pilier du développement WordPress. Ils permettent d’exécuter du code à des moments précis sans modifier le cœur de WordPress. Il existe deux grandes catégories : les actions et les filtres. Une action exécute une fonction à un moment donné. Un filtre reçoit une valeur, la modifie, puis la retourne. Par exemple, pour ajouter des fonctionnalités au thème, on utilise souvent l’action after_setup_theme :

function mon_theme_setup() {
    add_theme_support( 'title-tag' );
    add_theme_support( 'post-thumbnails' );
    register_nav_menus( array(
        'primary' => __( 'Menu principal', 'mon-theme' ),
    ) );
}
add_action( 'after_setup_theme', 'mon_theme_setup' );

Pour charger les ressources front-end, on utilise l’action wp_enqueue_scripts :

function mon_theme_enqueue_assets() {
    wp_enqueue_style(
        'mon-theme-style',
        get_stylesheet_uri(),
        array(),
        wp_get_theme()->get( 'Version' )
    );
    wp_enqueue_script(
        'mon-theme-navigation',
        get_template_directory_uri() . '/assets/js/navigation.js',
        array(),
        wp_get_theme()->get( 'Version' ),
        true
    );
}
add_action( 'wp_enqueue_scripts', 'mon_theme_enqueue_assets' );

Pour modifier une valeur fournie par WordPress, on utilise un filtre. Par exemple, on peut personnaliser la longueur des extraits :

function mon_theme_excerpt_length( $length ) {
    return 24;
}
add_filter( 'excerpt_length', 'mon_theme_excerpt_length' );

On peut aussi modifier le texte affiché à la fin d’un extrait :

function mon_theme_excerpt_more( $more ) {
    return '…';
}
add_filter( 'excerpt_more', 'mon_theme_excerpt_more' );

La logique métier doit être placée avec discernement. Un thème peut contenir une logique liée à la présentation : formats d’affichage, classes CSS dynamiques, tailles d’images, menus, zones de widgets, templates, options graphiques, styles éditeur. En revanche, les fonctionnalités qui doivent survivre à un changement de thème devraient plutôt être placées dans une extension. C’est le cas des types de contenus personnalisés, des taxonomies métier, des shortcodes stratégiques ou des règles de gestion propres à l’activité du site. Par exemple, déclarer un type de contenu projet directement dans le thème peut sembler pratique, mais cela pose un problème : si le thème est désactivé, le type de contenu disparaît de l’administration. Pour un vrai projet client, il est souvent préférable de placer cette déclaration dans une extension dédiée. Si le choix est tout de même fait de l’intégrer au thème, il faut au moins l’isoler dans un fichier dédié, comme inc/custom-post-types.php.

function mon_theme_register_project_post_type() {
    register_post_type( 'projet', array(
        'labels' => array(
            'name'          => __( 'Projets', 'mon-theme' ),
            'singular_name' => __( 'Projet', 'mon-theme' ),
        ),
        'public'       => true,
        'has_archive'  => true,
        'show_in_rest' => true,
        'supports'     => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
        'rewrite'      => array( 'slug' => 'projets' ),
    ) );
}
add_action( 'init', 'mon_theme_register_project_post_type' );

La propriété show_in_rest est importante pour la compatibilité avec l’éditeur de blocs. Sans elle, le type de contenu peut être moins bien intégré à Gutenberg et à l’API REST. La sécurité doit être intégrée dès le départ. Toute donnée affichée doit être échappée avec la fonction adaptée : esc_html() pour du texte, esc_attr() pour un attribut HTML, esc_url() pour une URL et wp_kses_post() pour du contenu HTML autorisé. Toute donnée reçue doit être validée, nettoyée et contrôlée avec des nonces si elle provient d’un formulaire. Un thème ne doit pas faire confiance aux champs personnalisés, aux options, aux métadonnées ou aux paramètres d’URL.

Voici un exemple simple de récupération sécurisée d’un champ personnalisé :

$subtitle = get_post_meta( get_the_ID(), '_mon_theme_subtitle', true );

if ( ! empty( $subtitle ) ) {
    echo '<p class="entry-subtitle">' . esc_html( $subtitle ) . '</p>';
}

Pour une URL personnalisée :

$button_url = get_post_meta( get_the_ID(), '_mon_theme_button_url', true );

if ( ! empty( $button_url ) ) {
    echo '<a class="button" href="' . esc_url( $button_url ) . '">';
    esc_html_e( 'En savoir plus', 'mon-theme' );
    echo '</a>';
}

Si le thème ajoute un formulaire front-end, il faut utiliser un nonce pour vérifier que la requête est légitime :

<form method="post">
    <?php wp_nonce_field( 'mon_theme_contact_action', 'mon_theme_contact_nonce' ); ?>

    <label for="message"><?php esc_html_e( 'Message', 'mon-theme' ); ?></label>
    <textarea id="message" name="message"></textarea>

    <button type="submit"><?php esc_html_e( 'Envoyer', 'mon-theme' ); ?></button>
</form>

Et côté traitement :

if (
    isset( $_POST['mon_theme_contact_nonce'] ) &&
    wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['mon_theme_contact_nonce'] ) ), 'mon_theme_contact_action' )
) {
    $message = isset( $_POST['message'] )
        ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) )
        : '';

    // Traitement du message.
}

Pour un thème moderne, il faut aussi penser à l’éditeur Gutenberg. Même dans un thème classique, on peut ajouter des styles éditeur afin que l’apparence dans l’administration se rapproche du rendu front-end :

function mon_theme_editor_styles() {
    add_theme_support( 'editor-styles' );
    add_editor_style( 'assets/css/editor.css' );
}
add_action( 'after_setup_theme', 'mon_theme_editor_styles' );

On peut également déclarer une palette de couleurs personnalisée pour encadrer les choix disponibles dans l’éditeur :

function mon_theme_editor_palette() {
    add_theme_support( 'editor-color-palette', array(
        array(
            'name'  => __( 'Primaire', 'mon-theme' ),
            'slug'  => 'primary',
            'color' => '#111827',
        ),
        array(
            'name'  => __( 'Accent', 'mon-theme' ),
            'slug'  => 'accent',
            'color' => '#2563eb',
        ),
    ) );
}
add_action( 'after_setup_theme', 'mon_theme_editor_palette' );

Dans un thème bloc, la logique change. Les templates sont placés dans templates, les parties réutilisables dans parts, et le fichier theme.json centralise les réglages globaux de design. Le fichier theme.json permet notamment de déclarer les couleurs, les typographies, les espacements, les tailles de police, les largeurs de contenu et certains paramètres de blocs.

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "color": {
      "palette": [
        {
          "slug": "primary",
          "color": "#111827",
          "name": "Primaire"
        },
        {
          "slug": "accent",
          "color": "#2563eb",
          "name": "Accent"
        }
      ]
    },
    "typography": {
      "fontSizes": [
        {
          "slug": "small",
          "size": "0.875rem",
          "name": "Petit"
        },
        {
          "slug": "large",
          "size": "2rem",
          "name": "Grand"
        }
      ]
    }
  }
}

Un thème bloc peut avoir une structure comme celle-ci :

mon-theme-bloc/
├── style.css
├── theme.json
├── templates/
│   ├── index.html
│   ├── single.html
│   ├── page.html
│   └── archive.html
└── parts/
    ├── header.html
    └── footer.html

Dans ce modèle, un fichier comme templates/single.html peut contenir des commentaires de blocs WordPress plutôt que du PHP :

<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
    <!-- wp:post-title {"level":1} /-->
    <!-- wp:post-date /-->
    <!-- wp:post-content /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

Cette approche permet à l’éditeur de site de modifier des zones globales comme l’en-tête, le pied de page ou les templates d’articles. Elle demande toutefois une autre manière de penser le thème : moins de PHP dans les templates, plus de configuration dans theme.json, plus de composants sous forme de blocs et une attention particulière à la cohérence des styles globaux. La performance front-end se joue ensuite dans les détails. Un thème ne doit pas charger tous les scripts sur toutes les pages. Un script de carrousel doit être chargé uniquement si un carrousel est présent. Un script de carte interactive doit être chargé uniquement sur la page qui l’utilise. Une feuille CSS très spécifique peut être séparée si elle ne concerne qu’un modèle particulier.

function mon_theme_page_assets() {
    if ( is_page_template( 'templates/contact.php' ) ) {
        wp_enqueue_script(
            'mon-theme-contact',
            get_template_directory_uri() . '/assets/js/contact.js',
            array(),
            wp_get_theme()->get( 'Version' ),
            true
        );
    }
}
add_action( 'wp_enqueue_scripts', 'mon_theme_page_assets' );

Les images doivent utiliser les tailles générées par WordPress plutôt que des fichiers originaux trop lourds. Il est préférable d’utiliser the_post_thumbnail() avec une taille adaptée, de déclarer des tailles personnalisées si nécessaire et de prévoir des dimensions cohérentes pour limiter les décalages de mise en page.

function mon_theme_image_sizes() {
    add_image_size( 'card-thumbnail', 640, 420, true );
    add_image_size( 'hero-large', 1600, 700, true );
}
add_action( 'after_setup_theme', 'mon_theme_image_sizes' );

La logique métier doit enfin rester testable et lisible. Lorsque le fichier functions.php devient trop long, il vaut mieux le transformer en chargeur de fichiers :

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

require_once get_template_directory() . '/inc/setup.php';
require_once get_template_directory() . '/inc/enqueue.php';
require_once get_template_directory() . '/inc/template-functions.php';
require_once get_template_directory() . '/inc/security.php';

Cette organisation permet de retrouver rapidement chaque responsabilité. Les déclarations du thème vont dans setup.php, les ressources front-end dans enqueue.php, les fonctions d’affichage dans template-functions.php et les protections spécifiques dans security.php. Sur un projet plus avancé, on peut aller plus loin avec des namespaces PHP, de l’autoloading Composer ou une architecture orientée objet, mais cette modularisation simple suffit déjà à améliorer nettement la maintenabilité. Développer les templates, les hooks et la logique métier d’un thème WordPress personnalisé revient donc à trouver le bon équilibre entre souplesse et discipline. Les templates doivent rester lisibles, les composants réutilisables, les hooks placés au bon endroit, les données sécurisées et les ressources chargées avec précision. C’est cette rigueur technique qui transforme un thème fonctionnel en véritable socle de production.

Xavier Deloffre

Xavier Deloffre

Fondateur de Facem Web, agence implantée à Arras et à Lille (Hauts-de-France), je suis spécialiste du Web Marketing, formateur expérimenté, et blogueur reconnu dans le domaine du Growth Hacking. Passionné par le référencement naturel (SEO) que j'ai découvert en 2009, j'imagine et développe des outils web innovants afin d'optimiser la visibilité de mes clients dans les SERPs. Mon objectif principal : renforcer leur notoriété en ligne par des stratégies digitales efficaces et créatives.

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Besoin de visibilité ?

☑️ Experts du référencement

☑️ + de 12 ans d’éxpérience

☑️ + 500 clients satisfaits

☑️ Création de sites

☑️ Audit SEO

☑️ Conseil SEO

☑️ Référencement de sites

☑️ Devis gratuit