Wenn Sie jemals „nur einen kleinen Button“ zu einer Seite hinzugefügt haben WordPressWenn man dann am Ende 200 KB JavaScript, eine Abhängigkeit von einem Framework und einen Cache hat, der sich nie leert, ist man auf das eigentliche Problem gestoßen: Clientseitige Interaktivität geht schnell auf Kosten der Komplexität.
Seit einigen Versionen bietet WordPress einen „kernfreundlicheren“ Ansatz: die Interactivity API. Die Idee ist theoretisch einfach: Clientseitiges Verhalten wird direkt im Markup deklariert (über die Interactivity API). data-wp-*), mit einem responsiven Shop, der von WordPress verwaltet wird, ohne React/Vue/Svelte in Ihrem Theme.
Das Problem / Der Bedarf
Konkretes Bedürfnis: Erstellung einer Frontend-Komponente (z. B. „Gefällt mir“, „Zähler“, „Filter“, „Akkordeon“, „In die Zwischenablage kopieren“).Laden progressiv“) interaktiv, ohne ein Framework einzubetten, und gleichzeitig kompatibel mit dem WordPress-Ökosystem (Cache, Themes, Block-Themes, Page Builder, Sicherheit).
Am Ende werden Sie es wissen:
- Entwerfen Sie ein kleines Plugin, das einen Block (oder ein überall verwendbares HTML-Rendering) mit der Interactivity API bereitstellt.
- Verwalten Sie clientseitigen Zustand (Speicher) und Aktionen (Handler) ohne externe Abhängigkeiten.
- Füge einen Endpunkt hinzu AJAX/REST sicher, um einen Zustand beizubehalten (z. B. wie einen Zähler).
- Debugging realer Anwendungsfälle: fehlerhaftes Enqueueing, aggressives Caching, schlecht gewählte Hooks, Builder-Konflikte.
Kurze Zusammenfassung
- Wir programmieren ein „Gefällt mir“-Button-Plugin (Zähler + Umschalter) basierend auf der Interactivity API, kompatibel mit WordPress 6.9.4+ und PHP 8.1+.
- Die Auszeichnung verwendet
data-wp-interactive,data-wp-on--click,data-wp-text,data-wp-class--*. - Der JS-Store verwaltet den lokalen Zustand (optimistische Benutzeroberfläche) und synchronisiert diesen anschließend über die REST-API.
- Auf der PHP-Seite: REST-Endpunkt
/wp-json/bpcab/v1/likeREST-Nonce, Berechtigungen, Bereinigung/Maskierung. - Wir bieten eine Shortcode-Variante + Divi 5-Integrationen an / Elementor / Avada.
Wann sollte diese Lösung verwendet werden?
- Sie wünschen sich eine unkomplizierte Interaktivität (Umschaltfunktion, Zähler, UI-Zustand) ohne ein React-Bundle.
- Sie liefern ein Theme oder Plugin, das für verschiedene Websites (Website-Builder, Cache, CDN) bestimmt ist, und möchten das Risiko von JS-Konflikten reduzieren.
- Sie benötigen ein SSR-Rendering (HTML, generiert von PHP), das auch ohne JS nutzbar bleibt und sich dann durch JS „verbessert“ (progressive Enhancement).
- Sie möchten sich an die WordPress-Muster halten (Enqueue, REST, Nonces) und vermeiden, dass eine „Mini-SPA“ in einer Ecke entsteht.
Wann diese Lösung NICHT verwendet werden sollte
- Sie entwickeln eine umfangreiche Single-Page-Anwendung (SPA) mit Routing, komplexen Formularen und globalen Berichtsfunktionen. Ein Framework (oder zumindest eine robustere Architektur) wäre hierfür besser geeignet.
- Sie müssen sehr alte Browser unterstützen (die Interactivity API folgt modernen Standards; überprüfen Sie Ihre Einschränkungen).
- Die Interaktivität wird bereits durch ein Plugin (z. B. WooCommerce-Blöcke, spezielle Facetten) bereitgestellt. Das Rad neu zu erfinden ist wartungsintensiv.
- Sie benötigen Echtzeitfunktionen (WebSocket, Präsenzstatus, Zusammenarbeit). Die Interaktivitäts-API ist kein Ersatz für eine Echtzeitschicht.
Voraussetzungen / vor Beginn
- Mindestens WordPress 6.9.4 (April 2026), PHP 8.1+.
- Eine Testumgebung (lokal oder Staging) ist erforderlich. Vermeiden Sie es, solchen Code direkt in der Produktionsumgebung zu testen: Ich habe schon erlebt, wie ein schlecht geschützter REST-Endpunkt innerhalb von Minuten massiv angegriffen wurde.
- Zugriff auf die Permalinks (Einstellungen), um die REST-API zu überprüfen und sie gegebenenfalls neu zu generieren.
- Nützliche Werkzeuge:
- Query Monitor zur Überprüfung von Hooks/REST/Perfs.
- Entwicklertools-Browser (Netzwerk + Konsole).
Offizielle Quellen, die man griffbereit haben sollte:
- Interaktivitäts-API (Block-Editor-Handbuch)
- REST-API-Handbuch
- register_rest_route()
- wp_enqueue_script ()
- PHP-Filter/Validierung (php.net)
Für die Kernüberwachung: Wenn Sie ungewöhnliches Verhalten feststellen (insbesondere bei der Interaktion mit dem Cache oder dem Editor), überprüfen Sie die Tickets auf core.trac.wordpress.org und die PRs auf github.com/WordPress/gutenberg.
Der naive Ansatz (und warum man ihn vermeiden sollte)
Die Version, die ich immer noch oft sehe: ein Knopf mit einem onclick inline, ein fetch zu admin-ajax.php, kein Nonce und ein Skript, das überall auf der Website geladen wird.
<?php
// Exemple à NE PAS copier : inline JS, pas de nonce, pas de contrôle de permission.
echo '<button onclick="likePost(' . get_the_ID() . ')">Like</button>';
?>
<script>
function likePost(postId){
fetch('/wp-admin/admin-ajax.php?action=like&post_id=' + postId)
.then(r => r.json())
.then(console.log);
}
</script>
Was geschieht hinter den Kulissen?
- Sicherheit: Endpunkt ohne Nonce = trivialer CSRF-Angriff. Und wenn Sie nicht validieren
post_idDamit öffnet man die Tür für willkürliche Schriften. - Performance: Skripte werden überall eingefügt, sind nicht fein steuerbar und lassen sich nur schwer richtig zwischenspeichern.
- Wartung: Wenn Sie die Komponente zugänglich (ARIA), testbar oder Builder-kompatibel machen müssen, müssen Sie von vorne beginnen.
- DX: Inline-Handler werden unüberschaubar, sobald man 2 Komponentenvarianten hat.
Die richtige Herangehensweise – eine Schritt-für-Schritt-Anleitung
Wir werden ein minimalistisches, übersichtliches Plugin mit einer funktionierenden „Gefällt mir“-Komponente entwickeln:
- Ohne JS: Der Button wird angezeigt (und Sie können eine Ausweichlösung festlegen).
- Mit JS: sofortiges Umschalten + aktualisierter Zähler, dann REST-Synchronisierung.
Schritt 1 – Plugin erstellen
Einen Ordner erstellen wp-content/plugins/bpcab-interactivity-like/ avec:
bpcab-interactivity-like.phpassets/like.js
Schritt 2 – Definition des „interaktiven“ HTML-Renderings
Die Interaktivitäts-API basiert auf Attributen data-wp-* im Markup. Am wichtigsten: data-wp-interactive wodurch ein Store-Namespace an einen DOM-Teilbaum „angehängt“ wird.
Wir werden einen Button und einen Zähler erstellen. Der Button:
- löst eine Aktion aus über
data-wp-on--click - zeigt dynamischen Text an über
data-wp-text - eine CSS-Klasse umschalten über
data-wp-class--is-liked
Schritt 3 — Das Skript korrekt laden (gezielte Einreihung)
Der klassische Fehler: JavaScript auf jeder Seite laden. Hier laden wir das Skript nur, wenn der Inhalt unseren Shortcode enthält (oder unser Block gerendert wird). Um es einfach und zuverlässig zu halten, gehen wir wie folgt vor:
- einen Kurzcode bereitstellen
[bpcab_like] - seine Anwesenheit erkennen durch
has_shortcode()rechtzeitig
Hinweis: Bei stark integrierten Websites (Elementor/Divi) kann die Inhaltsgenerierung anders aussehen. Im Folgenden finden Sie einige Beispiele.
Schritt 4 – Einen sicheren REST-Endpunkt bereitstellen
Wir vermeiden admin-ajax.php In diesem Fall ist die REST-API übersichtlicher, cachefreundlicher und besser ausgestattet (HTTP-Code, JSON, Authentifizierung). Wir erstellen eine Route:
POST /wp-json/bpcab/v1/likemitpost_idetdelta(+1 oder -1)
Wir schützen mit:
- REST Nonce (
wp_create_nonce( 'wp_rest' )) - Strenge Validierung der Parameter
- Berechtigungen: Anonyme Besucher sind hier zwar erlaubt, die Auswirkungen sind jedoch begrenzt (Delta ±1, bestehender Beitrag, öffentlicher Typ). Für eine echte nutzerbezogene Persistenz wäre ein Identitätsmodell erforderlich (Benutzer-ID, signiertes Cookie oder strengere Server-Speicherung).
Schritt 5 – Interaktivität im Shop (optimistische Benutzeroberfläche + Rollback)
Das Muster, das ich oft verwende:
- Wir werden die Benutzeroberfläche umgehend aktualisieren (optimistisch).
- Wir senden die REST-Anfrage.
- Falls das fehlschlägt, setzen wir den Zustand zurück und protokollieren ihn ordnungsgemäß.
Vollständiger Code
Datei 1: PHP-Plugin
schaffen wp-content/plugins/bpcab-interactivity-like/bpcab-interactivity-like.php.
<?php
/**
* Plugin Name: BPCAB Interactivity Like
* Description: Exemple avancé Interactivity API : bouton Like sans framework, avec synchronisation REST.
* Version: 1.0.0
* Requires at least: 6.9
* Requires PHP: 8.1
* Author: BPCAB
*/
declare(strict_types=1);
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
final class BPCAB_Interactivity_Like_Plugin {
private const SCRIPT_HANDLE = 'bpcab-like-interactivity';
private const REST_NAMESPACE = 'bpcab/v1';
private const REST_ROUTE = '/like';
public function hooks(): void {
add_action( 'init', [ $this, 'register_shortcode' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'maybe_enqueue_assets' ] );
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
}
public function register_shortcode(): void {
add_shortcode( 'bpcab_like', [ $this, 'render_shortcode' ] );
}
/**
* Rend le composant Like.
* Utilisable dans un article, une page, un widget texte, ou via do_shortcode().
*/
public function render_shortcode( array $atts = [] ): string {
$atts = shortcode_atts(
[
'post_id' => 0,
'label_like' => 'J’aime',
'label_unlike' => 'Je n’aime plus',
],
$atts,
'bpcab_like'
);
$post_id = (int) $atts['post_id'];
if ( $post_id <= 0 ) {
$post_id = get_the_ID() ? (int) get_the_ID() : 0;
}
if ( $post_id <= 0 ) {
return '';
}
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return '';
}
// Compteur stocké en post meta. Pour un site à fort trafic, préférez une table dédiée ou un agrégat asynchrone.
$likes = (int) get_post_meta( $post_id, '_bpcab_likes', true );
if ( $likes < 0 ) {
$likes = 0;
}
// État initial "liked" : ici, on ne persiste pas par utilisateur.
// On part sur false. Pour une vraie UX, vous pouvez lire un cookie signé ou une table user/post.
$initial_liked = false;
// Données initiales injectées dans le DOM (JSON) pour hydrater le store côté client.
$initial_state = [
'postId' => $post_id,
'likes' => $likes,
'liked' => $initial_liked,
'labels' => [
'like' => (string) $atts['label_like'],
'unlike' => (string) $atts['label_unlike'],
],
];
// Escaping : on encode en JSON, puis on échappe pour attribut HTML.
$initial_state_json = wp_json_encode( $initial_state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
if ( false === $initial_state_json ) {
return '';
}
// Markup Interactivity API.
// data-wp-interactive="bpcab/like" : namespace du store côté client.
// data-wp-context : contexte initial (state) accessible via le store.
$html = '<div class="bpcab-like"';
$html .= ' data-wp-interactive="bpcab/like"';
$html .= ' data-wp-context="' . esc_attr( $initial_state_json ) . '"';
$html .= '>';
$html .= '<button type="button" class="bpcab-like__button"';
$html .= ' data-wp-on--click="actions.toggle"';
$html .= ' data-wp-class--is-liked="state.liked"';
$html .= '>';
$html .= '<span class="bpcab-like__label" data-wp-text="state.liked ? state.labels.unlike : state.labels.like"></span>';
$html .= '</button>';
$html .= '<span class="bpcab-like__count" aria-live="polite">';
$html .= '<strong data-wp-text="state.likes">' . esc_html( (string) $likes ) . '</strong>';
$html .= '</span>';
$html .= '</div>';
return $html;
}
/**
* Enqueue conditionnel : on charge le JS seulement si le shortcode est présent.
* Attention : sur certaines pages builder, le contenu n'est pas dans post_content.
*/
public function maybe_enqueue_assets(): void {
if ( is_admin() ) {
return;
}
$post = get_post();
if ( ! $post ) {
return;
}
// Détection simple : shortcode présent dans post_content.
// Variante builder plus bas.
if ( ! has_shortcode( (string) $post->post_content, 'bpcab_like' ) ) {
return;
}
$asset_url = plugin_dir_url( __FILE__ ) . 'assets/like.js';
$asset_path = plugin_dir_path( __FILE__ ) . 'assets/like.js';
$ver = file_exists( $asset_path ) ? (string) filemtime( $asset_path ) : '1.0.0';
// Dépendances : le runtime Interactivity est fourni par WordPress (paquet @wordpress/interactivity).
// Le handle exact peut évoluer selon les versions ; en pratique WP expose les scripts nécessaires
// quand vous utilisez l'Interactivity API dans les blocs.
//
// Ici, on reste robuste : pas de dépendance forcée, mais on s'appuie sur le fait que WP charge
// le runtime interactivity quand le markup data-wp-interactive est présent.
wp_enqueue_script(
self::SCRIPT_HANDLE,
$asset_url,
[],
$ver,
[
'in_footer' => true,
'strategy' => 'defer',
]
);
// Données globales minimales : endpoint REST + nonce.
// Le nonce 'wp_rest' est le standard pour REST API.
wp_add_inline_script(
self::SCRIPT_HANDLE,
'window.BPCAB_LIKE = ' . wp_json_encode(
[
'restUrl' => esc_url_raw( rest_url( self::REST_NAMESPACE . self::REST_ROUTE ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
],
JSON_UNESCAPED_SLASHES
) . ';',
'before'
);
// Un peu de CSS minimal via inline (vous pouvez le sortir dans un fichier).
$css = '.bpcab-like{display:flex;gap:.6rem;align-items:center}.bpcab-like__button.is-liked{font-weight:700}.bpcab-like__count{opacity:.85}';
wp_register_style( 'bpcab-like-inline', false, [], $ver );
wp_enqueue_style( 'bpcab-like-inline' );
wp_add_inline_style( 'bpcab-like-inline', $css );
}
public function register_rest_routes(): void {
register_rest_route(
self::REST_NAMESPACE,
self::REST_ROUTE,
[
'methods' => 'POST',
'callback' => [ $this, 'rest_like' ],
'permission_callback' => [ $this, 'rest_permission' ],
'args' => [
'post_id' => [
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
'validate_callback' => function ( $value ) {
return is_numeric( $value ) && (int) $value > 0;
},
],
'delta' => [
'type' => 'integer',
'required' => true,
'sanitize_callback' => function ( $value ) {
$value = (int) $value;
if ( 1 === $value ) {
return 1;
}
if ( -1 === $value ) {
return -1;
}
return 0;
},
'validate_callback' => function ( $value ) {
$value = (int) $value;
return 1 === $value || -1 === $value;
},
],
],
]
);
}
/**
* Permission : on accepte les visiteurs anonymes, mais on exige un nonce REST valide.
* Si vous voulez limiter aux utilisateurs connectés : return is_user_logged_in();
*/
public function rest_permission( WP_REST_Request $request ): bool|WP_Error {
$nonce = $request->get_header( 'X-WP-Nonce' );
if ( ! $nonce ) {
return new WP_Error( 'bpcab_like_missing_nonce', 'Nonce manquant.', [ 'status' => 403 ] );
}
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
return new WP_Error( 'bpcab_like_invalid_nonce', 'Nonce invalide.', [ 'status' => 403 ] );
}
return true;
}
public function rest_like( WP_REST_Request $request ): WP_REST_Response|WP_Error {
$post_id = (int) $request->get_param( 'post_id' );
$delta = (int) $request->get_param( 'delta' );
$post = get_post( $post_id );
if ( ! $post || 'publish' !== $post->post_status ) {
return new WP_Error( 'bpcab_like_invalid_post', 'Article introuvable.', [ 'status' => 404 ] );
}
// Optionnel : limiter aux post types publics.
$post_type_obj = get_post_type_object( (string) $post->post_type );
if ( ! $post_type_obj || ! $post_type_obj->public ) {
return new WP_Error( 'bpcab_like_forbidden_type', 'Type de contenu non autorisé.', [ 'status' => 403 ] );
}
// Mise à jour robuste.
// Note : update_post_meta n'est pas atomique. Sur très fort trafic, vous pouvez avoir des collisions.
// Pour un compteur critique, utilisez une table custom + requête SQL atomique, ou un système de queue.
$current = (int) get_post_meta( $post_id, '_bpcab_likes', true );
$new = $current + $delta;
if ( $new < 0 ) {
$new = 0;
}
update_post_meta( $post_id, '_bpcab_likes', $new );
return new WP_REST_Response(
[
'postId' => $post_id,
'likes' => $new,
],
200
);
}
}
add_action(
'plugins_loaded',
static function (): void {
( new BPCAB_Interactivity_Like_Plugin() )->hooks();
}
);
Datei 2: Interaktionsskript
schaffen wp-content/plugins/bpcab-interactivity-like/assets/like.js.
/* global BPCAB_LIKE */
(function () {
'use strict';
/**
* Interactivity API :
* On enregistre un store "bpcab/like" qui expose state/actions.
* Le state initial provient de data-wp-context (injecté côté PHP).
*
* Référence : https://developer.wordpress.org/block-editor/reference-guides/interactivity-api/
*/
const { store, getContext } = window.wp && window.wp.interactivity ? window.wp.interactivity : {};
if (!store || !getContext) {
// Cas réel : le runtime interactivity n'est pas chargé (mauvais enqueue, optimisation agressive, plugin cache).
// On évite de casser la page.
return;
}
async function postLike({ postId, delta }) {
const res = await fetch(BPCAB_LIKE.restUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': BPCAB_LIKE.nonce
},
body: JSON.stringify({ post_id: postId, delta })
});
if (!res.ok) {
const text = await res.text().catch(() => '');
const err = new Error('REST error ' + res.status + ' ' + res.statusText);
err.details = text;
throw err;
}
return await res.json();
}
store('bpcab/like', {
state: {
get postId() {
return getContext().postId;
},
get likes() {
return getContext().likes;
},
get liked() {
return getContext().liked;
},
get labels() {
return getContext().labels || { like: 'J’aime', unlike: 'Je n’aime plus' };
}
},
actions: {
/**
* Toggle optimiste :
* - on inverse liked
* - on ajuste likes (+1/-1)
* - on synchronise REST
* - rollback en cas d'erreur
*/
async toggle() {
const ctx = getContext();
const previousLiked = !!ctx.liked;
const previousLikes = Number.isFinite(ctx.likes) ? ctx.likes : 0;
const nextLiked = !previousLiked;
const delta = nextLiked ? 1 : -1;
// Mise à jour optimiste.
ctx.liked = nextLiked;
ctx.likes = Math.max(0, previousLikes + delta);
try {
const data = await postLike({ postId: ctx.postId, delta });
// On resynchronise avec la vérité serveur.
if (data && typeof data.likes === 'number') {
ctx.likes = data.likes;
}
} catch (e) {
// Rollback.
ctx.liked = previousLiked;
ctx.likes = previousLikes;
// Log discret. En prod, vous pouvez envoyer vers un logger.
// Ne faites pas d'alert() agressif.
console.error('[bpcab/like] Échec synchronisation', e);
}
}
}
});
})();
Code-Erklärung
Allgemeine (einfache) Logik
PHP rendert eine "normale" HTML-Datei (einen Button + einen Zähler) und fügt Folgendes hinzu:
data-wp-interactive="bpcab/like"WordPress mitteilen: „Dieser Unterbaum verwendet den bpcab/like-Speicher“.data-wp-context="...json..."um den Ausgangszustand (postId, likes, liked, labels) einzufügen.data-wp-on--click="actions.toggle"um den Klick mit einer JS-Aktion zu verknüpfen.data-wp-text="..."einen aus dem Zustand berechneten Wert anzeigen.
Das JavaScript speichert einen „bpcab/ähnlichen“ Datenspeicher. Beim Klick des Benutzers wird der Kontext (reaktiv) geändert, anschließend wird die REST-API aufgerufen, um den Zähler zu speichern.
Technische Details (Hooks, Sicherheit, WP 6.9.4-Integration)
add_shortcode()Dies ist für ein portables Beispiel praktisch. Im Produktivbetrieb verwendet man häufig einen dynamischen Block (render_callback) für eine feinere Steuerung innerhalb des Editors.wp_enqueue_scriptsWir ermitteln von vorn. Der sensible Punkt ist die Anwesenheitserkennung. Wenn Ihr Bauunternehmen den Shortcode nicht einfügtpost_contentDas Skript wird nicht geladen.Die folgenden Varianten werden im Folgenden beschrieben.- REST API :
register_rest_route()legt die Straße frei.permission_callbackÜberprüfen Sie den NonceX-WP-Nonce(CSRF).- Strenge Validierung von
deltainnerhalb von ±1. Ohne diese Genauigkeit sieht man, wie Leute Beiträge veröffentlichen.delta=999999sobald der Endpunkt öffentlich ist.
- Rennbedingungen Ein Zähler für Post-Metadaten ist nicht atomar. Bei hohem Datenverkehr können zwei gleichzeitige Abfragen einen Wert überschreiben. Für einen typischen Blog ist dies akzeptabel; für ein Medienunternehmen mit hohem Datenaufkommen verwende ich eine benutzerdefinierte Tabelle und atomare Abfragen (oder einen Puffer/eine Warteschlange).
Warum data-wp-context statt wp_localize_script zum Beispiel
wp_localize_script Die globale Speicherung (über einen Handle) wird bei 10 Komponenten auf derselben Seite unübersichtlich. Hier hingegen verfügt jede Instanz über ihr eigenes JSON. Dies ist deutlich besser skalierbar, wenn Artikel in einer Schleife durchlaufen werden (z. B. eine Kategorieseite mit 20 „Gefällt mir“-Angaben).
Varianten und Anwendungsfälle
Variante 1 – Mehrere Schaltflächen auf einer Seite (Schleife) ohne komplexes globales JavaScript
Du kannst Geben [bpcab_like post_id="123"] in einer Schleife oder generieren Sie das HTML über PHP. Jede Komponente hat ihre eigene data-wp-contextDer Speicher wird gemeinsam genutzt, der Kontext jedoch pro Instanz: Genau das ist der Punkt.
Variante 2 – Speicherung der „Gefällt mir“-Angaben pro (angemeldetem) Benutzer anstelle eines einfachen Zählers.
Das von mir verwendete Muster:
- Wenn der Benutzer angemeldet ist: Speichern Sie in den Benutzermetadaten eine Liste (oder relationale Tabelle) der mit „Gefällt mir“ markierten Beiträge.
- Die REST-Endpunktprüfungen
is_user_logged_in()und verwendenget_current_user_id(). - Der Gesamtzähler bleibt in den Beitragsmetadaten (oder der Tabelle) erhalten, aber der Status „Gefällt mir“ wird zuverlässig.
Warnung: Die Liste in den Benutzermetadaten kann sehr lang werden. Auf großen Websites wird eine separate relationale Tabelle (Benutzer-ID, Beitrags-ID, Erstellungsdatum) mit Index empfohlen.
Variante 3 – „Nicht-persistenter“ Modus (nur Benutzeroberfläche) für Akkordeons/Registerkarten
Wenn Sie ausschließlich die Benutzeroberfläche (Akkordeon, Tabs) benötigen, entfernen Sie den REST-Endpunkt und behalten Sie nur Folgendes bei:
data-wp-on--clickeinen booleschen Wert im Kontext änderndata-wp-class--is-open/data-wp-textden Zustand widerspiegeln
Meiner Erfahrung nach ist dies der beste Einstiegspunkt: kein Backend, keine Sicherheitsvorkehrungen, und Sie validieren Ihre Enqueue/Cache-Kompatibilitätspipeline.
Divi 5 / Elementor / Avada-Kompatibilität
Die Stelle, die am häufigsten kaputt geht: Das Skript konnte nicht geladen werden. weil Ihr endgültiger Inhalt den Shortcode nicht enthält post_content.
Divi 5
Divi kann Layouts in Meta-Tags speichern, und das endgültige Rendering ist nicht immer über [hier fehlende Information] erkennbar. has_shortcode( $post->post_content )Zwei robuste Optionen:
- Eine „große“ Abfrage ist nur auf Seiten zulässig, auf denen das Modul bekanntermaßen erscheint (z. B. über eine bedingte Vorlage).
- Oder die Anwesenheit des zum Zeitpunkt des Renderns gerenderten Markups (Puffer) erkennen – anfälliger.
Ein pragmatischer Ansatz, den ich oft verwendet habe: das Hinzufügen einer kleinen „Force-Enqueue“-Einstellung über einen Filter.
<?php
// À mettre dans votre plugin (ou un mu-plugin) si Divi ne déclenche pas has_shortcode().
add_filter( 'bpcab_like_force_enqueue', function( bool $force ): bool {
if ( function_exists( 'et_theme_builder_get_template_layouts' ) ) {
// Indice que Divi est actif. À affiner selon votre contexte.
return true;
}
return $force;
}, 10, 1 );
Dann anpassen maybe_enqueue_assets() Um diesen Filter anzuwenden (falls Sie diese Variante wünschen, gehen Sie bitte korrekt vor). Ich habe ihn nicht standardmäßig eingebunden, um das Plugin so schlank wie möglich zu halten.
Elementor
Bei Elementor werden Inhalte häufig aus Meta-Tags generiert. Zwei elegante Lösungen:
- Erstellen Sie ein dediziertes Elementor-Widget, das das Markup rendert (und eine Skriptabhängigkeit über die Elementor-API registriert).
- Oder Abfrage auf Elementor-Seiten über eine Bedingung (Plugin-Erkennung + Metadaten).
Wenn Sie sich für die einfache Erkennung entscheiden:
<?php
// Exemple : détecter une page construite avec Elementor.
$is_elementor = (bool) get_post_meta( get_the_ID(), '_elementor_edit_mode', true );
Avada (Fusion Builder)
Avada verfügt über eigene Shortcodes und eine eigene Rendering-Pipeline. Die zuverlässigste Methode: Verwenden Sie den Shortcode. [bpcab_like] direkt in einen „Code“- oder „Shortcode“-Block/-Element.
Falls Avada aggressiv minifiziert/verkettet, überprüfen Sie Folgendes:
- Das Skript wird nicht verschoben nach
headohnedefer - Die Interaktivitäts-Laufzeitumgebung wird durch die Optimierung nicht „entfernt“.
Prüfungen nach der Installation
- Auf einer Seite mit
[bpcab_like]Entwicklertools öffnen > Netzwerk:- Überprüfen Sie das.
assets/like.jsist vollständig geladen (Status 200) - Überprüfen Sie das.
POST /wp-json/bpcab/v1/likeGibt bei Klick 200 zurück
- Überprüfen Sie das.
- Entwicklertools > Konsole:
- kein Fehler
window.wp.interactivityundefiniert
- kein Fehler
- Auf der WordPress-Seite:
- die Meta
_bpcab_likesaktualisiert sich selbst (Sie können dies über WP-CLI oder ein Meta-Plugin überprüfen).
- die Meta
Schnelldiagnosetabelle
| Symptom | Mögliche Ursache | Überprüfung | Lösung |
|---|---|---|---|
| Der Button wird angezeigt, reagiert aber nicht. | Laufzeitinteraktivität nicht geladen oder JavaScript nicht geladen | Konsole: window.wp.interactivity Existiert es? Netzwerk: like.js Aufladung ? |
Korrigieren Die Warteschlange, JS-Optimierung deaktivieren, Builder prüfen |
| Fehler 403 auf der REST-Route | Fehlende/ungültige Nonce | Netzwerk: Header X-WP-Nonce Hier ? |
überprüfen wp_add_inline_scriptHTML-Cache, CDN, das Header entfernt |
| Der Zähler „springt“ rückwärts. | Rollback nach REST-Fehler | Konsole: Protokoll [bpcab/like] Échec synchronisation |
Überprüfen Sie REST-URLs, Permalinks, WAF, CORS und Sicherheits-Plugins. |
| Der Zähler liefert unter Last ungenaue Werte. | Race Condition auf Post-Meta | Simulieren Sie gleichzeitige Klicks (k6/ab) und vergleichen Sie | Benutzerdefinierte Tabelle + atomare Aktualisierung oder asynchrone Aggregation |
Wenn es nicht funktioniert
- Überprüfen Sie, wo Sie den Code eingefügt haben. Dieses Plugin muss vorhanden sein
wp-content/plugins/...und aktiviert. Ich habe bereits gesehen, wie die PHP-Datei in das Theme eingefügt wurde, dann heißt es aber: „Es funktioniert auf einer Seite, aber nicht auf der anderen.“ - PHP prüfen In PHP-Versionen vor 8.1 können Typfehler auftreten. Siehe
wp-content/debug.logsiWP_DEBUGist aktiviert. - Überprüfen Sie die Permalinks Falls die REST-API einen 404-Fehler zurückgibt, gehen Sie zu Einstellungen > Permalinks und speichern Sie die Einstellungen (ohne Änderungen vorzunehmen). Dies ist ein häufiges Problem bei Migrationen.
- JavaScript-Optimierung vorübergehend deaktivieren (Cache/Minify): Ich habe häufig festgestellt, dass die Laufzeitinteraktivität durch eine Verkettung, die die Ladefolge ändert, „unterbrochen“ wird.
- Testen Sie den REST-Endpunkt mit curl. :
curl -i -X POST "https://example.com/wp-json/bpcab/v1/like"
-H "Content-Type: application/json"
-H "X-WP-Nonce: VOTRE_NONCE"
--data '{"post_id":123,"delta":1}'
Falls Sie keine Nonce zur Hand haben, testen Sie zumindest, ob die Route existiert (sie sollte den Fehlercode 403 „Nonce fehlt“ zurückgeben, was ein gutes Zeichen ist).
Häufige Fallstricke und Fehler
| Fehler | Verursachen | Lösung |
|---|---|---|
Uncaught TypeError: Cannot destructure property 'store' ... |
window.wp.interactivity nicht vorhanden (Laufzeitumgebung nicht geladen) |
Überprüfen Sie die Warteschlange, deaktivieren Sie die Minifizierung und stellen Sie sicher, dass WP die Interaktivitätsskripte lädt. |
| Das Skript wird nie geladen. | has_shortcode() wird nicht erkannt (Builder, gerendert über Meta) |
Fügen Sie einen Bedingungsgenerator (Elementor/Divi/Avada) hinzu oder reihen Sie ihn über ein dediziertes Widget/Modul ein. |
403 Nonce invalide |
HTML-Code, der mit einer abgelaufenen Nonce zwischengespeichert wurde, oder Seite, die über den vollständigen Seitencache ausgeliefert wurde. | Schließen Sie die Seite vom Cache aus, generieren Sie die Nonce über einen öffentlichen Endpunkt oder beschränken Sie den Zugriff auf angemeldete Benutzer. |
SyntaxError: Unexpected token in data-wp-context |
JSON falsch kodiert/maskiert, fehlerhafte Anführungszeichen | Immer durchgehen wp_json_encode puis esc_attr |
| Schwerwiegender Fehler beim Kopieren und Einfügen | Fehlende Klammer/Semikolon oder Datei im falschen Ordner | Mit einem Linter validieren, WP_DEBUG auf der Staging-Umgebung aktivieren, Syntax korrigieren |
| Der Zähler wird negativ. | Delta nicht validiert oder schneller Doppelklick | Senden delta ±1, Begrenzung auf 0 sowohl serverseitig als auch clientseitig |
| Konflikt mit einem Sicherheits-/WAF-Plugin | Blockieren von REST-POST-Anfragen oder Headern | Route auf die Whitelist setzen /wp-json/bpcab/v1/likeWAF-Protokolle prüfen |
| Sie verwenden einen alten Codeausschnitt aus dem Jahr 2023. | API/Handles geändert, Praktiken veraltet | Zielversion: WP 6.9.4+, basierend auf der aktuellen Interaktivitätsdokumentation |
Sicherheits-, Leistungs- und Wartungstipps
Sicherheit
- REST-Nonce erforderlich Wenn Ihr Endpunkt Daten verändert. Ohne Nonce: CSRF-Angriff.
- Einstellungen bestätigen : Hier
deltaist streng ±1. Es ist eine einfache, aber effektive Barriere. - Denken Sie an den Missbrauch Ein öffentlicher Zähler kann manipuliert werden. Wenn die Kennzahl einen geschäftlichen Wert hat, fügen Sie Folgendes hinzu:
- Ratenbegrenzung (auf Reverse-Proxy-/WAF-Ebene)
- Speicherung pro Benutzer (angemeldet) oder signiertem Cookie
- Bot-Erkennung
Leistung
- Bedingte Abfrage: Laden vermeiden
like.jsüberall. - Vermeiden Sie unnötige REST-Anfragen: Sie können eine Entprellung hinzufügen, wenn Sie Mehrfachklicks zulassen (hier schalten wir um, daher stabil).
- Hoher Datenverkehr: Metadaten posten + nicht-atomare Aktualisierung. Bei Lastspitzen empfiehlt sich eine benutzerdefinierte Tabelle mit atomarer SQL-Aktualisierung (oder verzögerter Aggregation).
Wartung
- Halten Sie den Shop in einem übersichtlichen Namensraum (
bpcab/like), vermeiden Sie generische Namen (app). - Vermeiden Sie es, Ihre Logik an einen Builder zu koppeln. Erstellen Sie gegebenenfalls eine Integrationsschicht (Widget/Modul).
- Die Entwicklungen der Interaktivitäts-API lassen sich über Gutenberg (PR) und das Handbuch verfolgen. Änderungen werden zuerst in Gutenberg erfasst und anschließend in den Kern integriert.
Ressourcen
- Interaktivitäts-API (developer.wordpress.org)
- REST-API-Handbuch
- register_rest_route() (Referenz)
- wp_create_nonce() (Referenz)
- wp_verify_nonce() (Referenz)
- WordPress Core Trac (Fehlerverfolgung)
- Gutenberg-Repository (PR Interaktivität)
- filter_var() (php.net)
- WordPress.org-Supportforen (Fallbeispiele aus der Praxis)
FAQ
Ersetzt die Interactivity API React in Gutenberg?
Nein. Gutenberg verwendet weiterhin React für den Editor. Die Interactivity API zielt primär auf die Frontend-Interaktivität von Blöcken/Komponenten ab und verfolgt ein schlankeres und deklarativeres Modell.
Warum mein window.wp.interactivity ist undefiniert?
In der Praxis liegt dies entweder an einem Problem mit der Warteschlange (das Skript wurde zu früh oder gar nicht geladen), an einer JavaScript-Optimierung, die die Reihenfolge durcheinanderbringt, oder an einem Builder, der Inhalte generiert, ohne Ihre Bedingungen zu erfüllen. Deaktivieren Sie vorübergehend aggressive Minifizierungs-/Verzögerungsoperationen und überprüfen Sie die Netzwerkeinstellungen.
Ist dies mit einem vollständigen Seitencache kompatibel?
Ja, für die Anzeige. Bei der Datenspeicherung ist jedoch Vorsicht mit der Nonce geboten: Wird die HTML-Seite aus dem Cache geladen und enthält sie eine abgelaufene Nonce, schlagen Ihre REST-POST-Anfragen fehl (403). Bei stark zwischengespeicherten Websites generiere ich die Nonce daher lieber über einen öffentlichen Endpunkt (schreibgeschützt) oder beschränke die Funktionalität auf angemeldete Benutzer.
Warum REST anstelle von admin-ajax verwenden?
REST bietet eine sauberere HTTP-Semantik, standardisierte JSON-Antworten und lässt sich besser in moderne Tools integrieren. Admin-Ajax ist zwar weiterhin gültig, entwickelt sich aber schnell zu einer Art Allzwecklösung, die schwer abzusichern und zu analysieren ist.
Kann man anstelle eines Shortcodes einen Gutenberg-Block verwenden?
Ja, und das ist oft vorzuziehen. Der Kern des Musters bleibt gleich: SSR-Rendering + Attribute data-wp-* + Store. Ich verwende hier einen Shortcode, damit Sie ihn in 5 Minuten testen können, auch in Buildern.
Wie verwaltet man mehrere verschiedene Zähler (Likes, Lesezeichen, Stimmen)?
Verwenden Sie separate Namensräume (bpcab/like, bpcab/bookmark) oder ein einzelner, kontextabhängig konfigurierter Speicher. Vermeiden Sie einen „monolithischen“ Speicher, wenn Ihre Komponenten unabhängig sind.
Ist der Zähler im Post-Meta zuverlässig?
Für einen typischen Blog: ja. Bei hohem Traffic: nein, es wird zu Konflikten kommen. Wechseln Sie zu einer benutzerdefinierten Tabelle und atomaren Aktualisierungen oder verwenden Sie eine asynchrone Aggregationsstrategie.
Wie kann ich diesen Code richtig testen?
Ich mache das normalerweise so:
- Staging + WP_DEBUG-Protokoll
- Manueller Test: Schnellklicks, Navigation, Browser-Cache
- REST-Test: curl (403 erwartet ohne Nonce, 200 mit Nonce)
- Cache-Kompatibilitätstest: Cache aktivieren, Ablaufdatum der Nonce prüfen
Warum nicht den Nuntius einsetzen? data-wp-context ?
Das ist zwar möglich, aber dadurch wird ein sensibler Wert in jeder Instanz dupliziert. Ich bevorzuge ein minimales globales Objekt und einen Kontext pro Komponente für den UI-Zustand. Bei sehr langen Seiten reduziert dies die HTML-Größe.
Funktioniert es auch in einem klassischen (Nicht-FSE-)Theme?
Ja. Die Interaktivitäts-API ist nicht auf Blockthemen beschränkt. Entscheidend ist, die Assets korrekt zu laden und das Markup mit den Attributen zu rendern. data-wp-*.
Was soll ich tun, wenn ein Snippets-Plugin den Code beschädigt?
Vermeiden Sie es, dieses Plugin in einen Code-Snippet einzufügen. Erstellen Sie stattdessen ein eigenständiges Plugin (wie dieses hier). Code-Snippets sind zwar praktisch, aber ich habe schon zu viele Websites abstürzen sehen, weil ein Snippet mit einer anderen PHP-Version aktiviert wurde oder beim Kopieren und Einfügen eine geschweifte Klammer fehlte.