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

Essayer maintenant

WordPress et l’API WebGPU : Exploiter le GPU du navigateur pour des expériences visuelles avancées dans Gutenberg

On entend souvent dire que WordPress, c’est bien pour les sites vitrines ou les blogs… mais certainement pas pour des expériences visuelles vraiment impressionnantes. Et si on vous disait que Gutenberg peut aujourd’hui exploiter directement le GPU du navigateur via l’API WebGPU, pour des animations dignes des meilleures productions web ? C’est exactement ce qu’on va explorer ici : de l’initialisation du contexte GPU jusqu’aux blocs prêts pour la production, avec des exemples concrets et des patterns solides.

WebGPU dans le navigateur : ce que ça change pour un développeur WordPress

Avant de se lancer dans du code, il faut poser les bases. WebGPU, c’est une technologie qui peut sembler réservée aux développeurs de jeux AAA ou aux chercheurs en machine learning — mais en réalité, elle est bien plus accessible (et bien plus utile) qu’on ne le pense pour un dev WordPress.

Qu’est-ce que l’API WebGPU et en quoi diffère-t-elle de WebGL ?

WebGPU est la nouvelle API graphique bas niveau standardisée par le W3C, conçue pour exposer directement les capacités du GPU du navigateur. Là où WebGL s’appuyait sur OpenGL ES — une API vieillissante pensée pour le rendu 3D — WebGPU adopte une approche moderne inspirée de Vulkan, Metal et Direct3D 12.

La différence concrète ? WebGPU, c’est comme passer d’une API REST à du SQL direct : plus de contrôle, plus de responsabilité. On écrit ses shaders en WGSL (WebGPU Shading Language), un langage spécifiquement conçu pour ce contexte, bien plus lisible et sécurisé que le GLSL de WebGL.

Mais surtout, WebGPU ne se limite pas au rendu 3D. On peut l’utiliser pour :

  • Le traitement d’images en temps réel (filtres, corrections colorimétriques, effets)
  • Des calculs parallèles massifs (machine learning côté client, simulations)
  • Des effets visuels avancés sur des éléments HTML/CSS classiques

C’est cette polyvalence qui rend WebGPU particulièrement intéressant dans un contexte WordPress.

État du support navigateur en 2026 et considérations de compatibilité

En 2026, le support de WebGPU a bien progressé — mais il reste quelques zones d’ombre à connaître avant de se lancer.

Voici l’état des lieux :

  • Chrome 113+ : activé par défaut, le support le plus complet et le plus stable
  • Edge : aligné sur Chromium, donc très bon support
  • Safari 18+ : support partiel, quelques fonctionnalités manquantes ou comportements différents
  • Firefox : toujours derrière un flag (dom.webgpu.enabled), à activer manuellement

Concrètement, si votre audience est majoritairement sur Chrome (ce qui est le cas pour la grande majorité des sites), vous couvrez déjà l’essentiel. Par contre, ne déployez pas une expérience WebGPU sans prévoir un fallback propre — une image statique, un canvas 2D classique, ou simplement un message d’information. La détection se fait simplement avec navigator.gpu : si c’est undefined, on bascule sur l’alternative.

Pourquoi Gutenberg est un terrain idéal pour expérimenter WebGPU

C’est là que ça devient vraiment intéressant pour nous en tant que développeurs WordPress.

Gutenberg repose sur une architecture de blocs React. Chaque bloc est un composant isolé, avec son propre cycle de vie, ses propres props, son propre DOM. Et c’est exactement ce qu’il faut pour initialiser un contexte WebGPU proprement.

Pourquoi ? Parce qu’un GPUDevice doit être attaché à un <canvas> spécifique, initialisé une seule fois, et nettoyé correctement à la destruction du composant. Les hooks React — useEffect, useRef — sont parfaitement adaptés à ce pattern. On initialise le contexte au montage, on rend dans la boucle d’animation, on détruit tout au démontage. Propre, prévisible, maintenable.

Et bonus non négligeable : l’éditeur Gutenberg lui-même tourne dans un environnement moderne (Chrome ou Edge dans la quasi-totalité des cas), donc on peut expérimenter sans se soucier de la compatibilité côté éditeur. Le frontend, c’est une autre question — mais on y reviendra.

Créer un bloc Gutenberg qui exploite WebGPU : architecture et implémentation

On rentre maintenant dans le vif du sujet. Cette section est probablement la plus dense de l’article, mais je vais essayer de rendre ça aussi digeste que possible. L’idée, c’est de construire un bloc Gutenberg fonctionnel qui initialise un contexte WebGPU, exécute un shader WGSL et gère correctement son cycle de vie dans l’éditeur React.

Scaffolding du bloc avec @wordpress/create-block et structure des fichiers

On commence par générer le squelette du bloc avec l’outil officiel :

npx @wordpress/create-block my-webgpu-block --template @wordpress/create-block-tutorial-template

Une fois généré, on va organiser les fichiers de manière un peu plus structurée que la config par défaut. L’objectif : isoler toute la logique GPU dans un sous-dossier dédié pour ne pas polluer le composant React.

Voici la structure recommandée :

my-webgpu-block/
├── src/
│   ├── edit.js          ← composant React de l'éditeur
│   ├── save.js          ← rendu statique (ou placeholder)
│   ├── block.json
│   └── webgpu/
│       ├── renderer.js  ← initialisation et boucle de rendu
│       └── shaders/
│           └── fragment.wgsl  ← votre shader WGSL

Cette séparation est importante : renderer.js encapsule toute la complexité GPU, et edit.js reste un composant React lisible. C’est le genre d’architecture qui vous sauvera la mise quand vous devrez déboguer à 23h un pipeline de rendu qui refuse de compiler.

Initialisation du contexte WebGPU dans le composant Edit React

Dans edit.js, la première chose à faire c’est vérifier que WebGPU est bien disponible. On ne peut pas supposer que l’éditeur tournera toujours sur un navigateur compatible (voir section précédente sur le support navigateur en 2026).

// edit.js
import { useEffect, useRef } from '@wordpress/element';
import { initWebGPU } from './webgpu/renderer';

export default function Edit() {
    const canvasRef = useRef(null);
    const deviceRef = useRef(null);

    useEffect(() => {
        // Vérification de la disponibilité de WebGPU
        if (!navigator.gpu) {
            console.warn('WebGPU non supporté sur ce navigateur.');
            return;
        }

        let animationFrameId;

        async function setup() {
            // Demande d'un adaptateur GPU
            const adapter = await navigator.gpu.requestAdapter();
            if (!adapter) {
                console.error('Aucun adaptateur GPU disponible.');
                return;
            }

            // Création du device GPU
            const device = await adapter.requestDevice();
            deviceRef.current = device;

            // Configuration du canvas
            const canvas = canvasRef.current;
            const context = canvas.getContext('webgpu');
            const format = navigator.gpu.getPreferredCanvasFormat();

            context.configure({ device, format });

            // Lancement du renderer (voir renderer.js)
            animationFrameId = await initWebGPU(device, context, format, canvas);
        }

        setup();

        // Cleanup : CRITIQUE pour éviter les fuites mémoire
        return () => {
            cancelAnimationFrame(animationFrameId);
            if (deviceRef.current) {
                deviceRef.current.destroy();
                deviceRef.current = null;
            }
        };
    }, []);

    return (
        <div>
            <canvas ref={canvasRef} width={800} height={450} />
        </div>
    );
}

Le piège classique ici : oublier le return de cleanup dans useEffect. Dans l’éditeur Gutenberg, les composants React peuvent se monter et se démonter plusieurs fois (navigation entre blocs, undo/redo, etc.). Sans device.destroy(), vous accumulerez des contextes GPU orphelins jusqu’à ce que le navigateur se plaigne.

Écrire son premier shader WGSL et le brancher sur un canvas dans le bloc

Le WGSL (WebGPU Shading Language) remplace le GLSL de WebGL. La syntaxe est plus stricte, plus typée. Voici un exemple minimaliste qui génère un dégradé animé — parfait pour valider que le pipeline fonctionne avant de complexifier :

// shaders/fragment.wgsl

struct VertexOutput {
    @builtin(position) position: vec4f,
    @location(0) uv: vec2f,
};

@vertex
fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
    // Triangle couvrant tout l'écran (fullscreen triangle trick)
    var pos = array<vec2f, 3>(
        vec2f(-1.0, -1.0),
        vec2f( 3.0, -1.0),
        vec2f(-1.0,  3.0),
    );
    var out: VertexOutput;
    out.position = vec4f(pos[vi], 0.0, 1.0);
    out.uv = (pos[vi] + 1.0) * 0.5;
    return out;
}

// Uniform pour le temps (pour l'animation)
@group(0) @binding(0) var<uniform> time: f32;

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
    // Dégradé animé simple
    let r = in.uv.x;
    let g = in.uv.y;
    let b = 0.5 + 0.5 * sin(time * 2.0);
    return vec4f(r, g, b, 1.0);
}

Dans renderer.js, on charge ce shader, on compile le pipeline et on lance la boucle de rendu :

// webgpu/renderer.js
import shaderCode from './shaders/fragment.wgsl?raw'; // avec Vite/webpack raw-loader

export async function initWebGPU(device, context, format, canvas) {
    // Compilation du shader module
    const shaderModule = device.createShaderModule({ code: shaderCode });

    // Création du pipeline de rendu
    const pipeline = device.createRenderPipeline({
        layout: 'auto',
        vertex: { module: shaderModule, entryPoint: 'vs_main' },
        fragment: {
            module: shaderModule,
            entryPoint: 'fs_main',
            targets: [{ format }],
        },
        primitive: { topology: 'triangle-list' },
    });

    // Buffer uniform pour le temps
    const timeBuffer = device.createBuffer({
        size: 4,
        usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });

    const bindGroup = device.createBindGroup({
        layout: pipeline.getBindGroupLayout(0),
        entries: [{ binding: 0, resource: { buffer: timeBuffer } }],
    });

    let startTime = performance.now();
    let animationFrameId;

    function frame() {
        const elapsed = (performance.now() - startTime) / 1000;
        device.queue.writeBuffer(timeBuffer, 0, new Float32Array([elapsed]));

        const encoder = device.createCommandEncoder();
        const pass = encoder.beginRenderPass({
            colorAttachments: [{
                view: context.getCurrentTexture().createView(),
                clearValue: { r: 0, g: 0, b: 0, a: 1 },
                loadOp: 'clear',
                storeOp: 'store',
            }],
        });
        pass.setPipeline(pipeline);
        pass.setBindGroup(0, bindGroup);
        pass.draw(3); // fullscreen triangle
        pass.end();
        device.queue.submit([encoder.finish()]);

        animationFrameId = requestAnimationFrame(frame);
    }

    frame();
    return animationFrameId;
}

Un point d’attention : l’import ?raw du fichier WGSL fonctionne nativement avec Vite. Si vous êtes sur webpack (config par défaut de @wordpress/scripts), il faudra ajouter une règle raw-loader dans votre webpack.config.js personnalisé. C’est le genre de détail qui fait perdre 30 minutes si on ne le sait pas.

Gérer le cycle de vie React (useEffect, useRef) pour éviter les fuites mémoire GPU

C’est le point que les développeurs sous-estiment le plus, et c’est pourtant critique dans le contexte de Gutenberg. L’éditeur de blocs est une application React qui peut re-rendre vos composants fréquemment : ouverture du panneau latéral, modifications d’attributs, rechargement partiel…

Quelques règles à suivre absolument :

  • Toujours stocker le GPUDevice dans un useRef, pas dans un état React (useState). Un changement d’état déclenche un re-render, ce que vous voulez éviter pour des ressources GPU coûteuses.
  • Ne jamais configurer le canvas WebGPU en dehors d’un useEffect. L’initialisation GPU est asynchrone et doit être liée au cycle de montage du composant.
  • Libérer TOUTES les ressources dans le cleanup : device.destroy(), cancelAnimationFrame(), et si vous avez des buffers intermédiaires, les détruire explicitement (buffer.destroy()).
// Pattern de cleanup complet
useEffect(() => {
    // ... setup async

    return () => {
        // 1. Stopper la boucle de rendu
        cancelAnimationFrame(animationFrameId);
        // 2. Détruire les buffers
        if (timeBuffer) timeBuffer.destroy();
        // 3. Détruire le device (libère TOUTES les ressources GPU associées)
        if (deviceRef.current) {
            deviceRef.current.destroy();
            deviceRef.current = null;
        }
    };
}, []); // [] = exécuté une seule fois au montage

Autre piège courant : le redimensionnement du canvas. Si le bloc est redimensionné dans l’éditeur, la surface WebGPU doit être reconfigurée. Un canvas dont les dimensions CSS ne correspondent pas aux dimensions intrinsèques (canvas.width / canvas.height) produira un rendu flou ou mal cadré. Un ResizeObserver branché sur le canvas peut gérer ça proprement.

Et dernier point — important : tout ce code ne vit que dans edit.js. Le fichier save.js ne peut pas initialiser WebGPU (il s’exécute côté serveur PHP pour générer le HTML statique). Vous devrez y retourner soit un <canvas> vide avec les attributs nécessaires (si vous gérez l’initialisation frontend via un script enqueué), soit un simple placeholder visuel. Ne mettez aucune logique GPU dans save.js, vous aurez des erreurs de validation de bloc à coup sûr.

Cas d’usage concrets et bonnes pratiques pour la production

On a vu comment initialiser un GPUDevice et structurer un bloc Gutenberg proprement. Maintenant, passons aux choses sérieuses : des cas d’usage réels, ceux qui font briller les yeux des clients lors d’une démo. Et surtout, les bonnes pratiques qui font la différence entre un proof-of-concept qui tourne en local et un bloc qui tient en production.

Dégradés animés et effets de particules dans un bloc Hero

Le bloc Hero, c’est souvent la première chose qu’un visiteur voit. Autant en mettre plein la vue. Et WebGPU se prête parfaitement à ce cas d’usage : un fond de particules animées, fluides à 60fps, sans faire souffrir le CPU.

Le principe est simple. On gère un tableau de milliers de particules côté GPU — position, vélocité, couleur — via des buffers de type GPUBuffer. Le shader WGSL calcule la nouvelle position de chaque particule à chaque frame. Le CPU, lui, ne fait quasiment rien : il soumet juste des command buffers au GPU via device.createCommandEncoder(), puis appelle queue.submit(). La boucle de rendu ressemble à ça :

function renderLoop() {
  const commandEncoder = device.createCommandEncoder();
  const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
  passEncoder.setPipeline(pipeline);
  passEncoder.setVertexBuffer(0, particleBuffer);
  passEncoder.draw(PARTICLE_COUNT);
  passEncoder.end();
  device.queue.submit([commandEncoder.finish()]);
  requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);

Le résultat : un effet volumétrique, avec des particules qui dérivent et se dispersent de façon organique. Aucune bibliothèque externe, pas de Three.js, juste du WGSL et l’API WebGPU. C’est là que ça devient vraiment excitant.

Progressive enhancement : fallback Canvas 2D ou CSS quand WebGPU est absent

C’est le point critique pour la production. WebGPU n’est pas disponible partout — on l’a dit en introduction — et livrer un bloc avec un fond noir pour 30% de vos utilisateurs, ce n’est pas une option.

La détection est simple et doit être la première chose que votre bloc vérifie au montage :

useEffect(() => {
  if (!navigator.gpu) {
    // Fallback : animation CSS ou Canvas 2D
    activateCSSFallback();
    return;
  }
  initWebGPU();
}, []);

Pour le fallback, deux options selon le niveau d’effort souhaité :

  • Canvas 2D : on reproduit un effet de particules simplifié avec ctx.fillRect() et requestAnimationFrame. C’est moins performant pour de très grandes scènes, mais amplement suffisant pour quelques centaines de particules.
  • Animation CSS pure : quelques @keyframes bien pensés avec transform et opacity. Zéro JavaScript. C’est la solution la plus robuste et la plus légère — parfaite pour les appareils mobiles d’entrée de gamme.

On peut aussi combiner ça avec @supports en CSS pour certaines propriétés graphiques avancées, même si la détection de WebGPU reste côté JS uniquement.

Quelques bonnes pratiques à ne surtout pas négliger en production :

  • IntersectionObserver pour le rendu conditionnel : n’initialisez jamais WebGPU hors écran. Si le bloc Hero est en dessous de la fold, vous gaspillez des ressources GPU dès le chargement de la page. Lancez initWebGPU() uniquement quand le bloc entre dans le viewport — c’est une ligne de code qui change tout.
  • Throttling sur mobile : sur les appareils moins puissants, limitez la fréquence de rendu. Un simple compteur de frames dans la boucle requestAnimationFrame permet de ne rendre qu’une frame sur deux sur mobile, par exemple.
  • Documentation interne : un bloc WebGPU, ça peut sembler magique pour un autre développeur de l’équipe. Commentez votre code, expliquez la structure des shaders WGSL, et documentez clairement les prérequis navigateur dans le README du bloc.

Bon, soyons honnêtes : en 2026, WebGPU en production WordPress reste de la R&D avancée. Ce n’est pas ce qu’on met dans un site vitrine classique. Mais pour une agence qui veut se démarquer sur des projets premium — une expérience de marque, un portfolio interactif, une landing page de lancement de produit — c’est un argument différenciant réel. Et maîtriser le sujet maintenant, c’est prendre de l’avance sur une technologie qui va inévitablement se démocratiser.