Files
claude-regelwerk/AI/Regelwerk/seo.md
T
Pascal MichelandClaude Opus 5 e8472409e9 Automatischer Push: 25 Datei(en) geändert
- .claude/agents/executer.md
- .claude/agents/recherche.md
- .claude/skills/anleitung-straffen/SKILL.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/00-uebersicht.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-01-zaehler.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-02-sitzungsstart.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-03-regeltexte.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-04-bestandsskript.md
- .vault-tools/linkcheck/docs/plans/2026-09-29-waisen/task-05-bestandslauf.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/00-uebersicht.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-01-quorum-werkzeug.md
- .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/task-02-quorum-workflow.md
- … und 13 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-30 04:00:02 +02:00

91 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: resource
status: pending-review
title: SEO-Arbeit an WordPress-Kundensites über WPVibe
summary: Regeln für SEO-Audits und deren Umsetzung auf WordPress-Sites (Rank Math, Salient/WPBakery, WP Super Cache, WPCode) über den WPVibe-Connector — Arbeitsweise, Werkzeugwege, Messregeln, bekannte Fallen. Nur laden, wenn eine Website auditiert oder nach einem Audit umgebaut wird.
tags: [regelwerk, seo, wordpress, wpvibe, rank-math]
created: 2026-09-07
updated: 2026-09-29
---
# SEO-Arbeit an WordPress-Kundensites über WPVibe
Angeordnet am 2026-09-07. Laden, wenn eine Website auditiert (`/seo`, `/seo-audit`) oder ein Audit-Befund auf einer WordPress-Site umgesetzt wird. Nicht bei Code-Projekten, nicht bei Dokumenten, nicht beim Depot.
Alles hier stammt aus der Arbeit an einer Kundensite zwischen dem 02.09. und dem 07.09.2026 (Audits 26 → 82 technisch, Vollaudit 67 → 70). Die Fallgeschichten und Belege liegen im Harness-Gedächtnis; hier steht nur die Regel. Wo eine Regel nur für eine bestimmte Version oder einen bestimmten Hoster belegt ist, steht das dabei.
## 1 Arbeitsweise
1. **Audit-Befunde sind Behauptungen, keine Fakten.** Jeden Befund vor der Umsetzung live nachmessen (curl ohne Cache, Browser bei 375 und 1366 px). Am 07.09. waren von zehn Befunden eines Nach-Fix-Audits drei falsch: IndexNow lief, das Kachel-Clipping war nicht reproduzierbar, das „ungeplant aktivierte" Schema hatte Pascal selbst eingeschaltet. Audit-Subagenten zitieren außerdem Altbefunde aus `findings/*.md` früherer Läufe, die längst behoben sind.
2. **Eigene Erfolgsmeldungen sind auch Behauptungen.** Die Mittagssitzung vom 07.09. meldete „Team-Verlinkung live" und „Angebote gefixt"; beides stimmte nicht. Ein Nach-Fix-Audit läuft unabhängig und übernimmt nichts aus der Fix-Liste.
3. **Drei Sorten von Punkten sauber trennen:** was über den Connector geht (Inhalte, Optionen, Cache); was nur Pascal kann (`.htaccess`, Snippet-Freigabe und -Aktivierung, WebFTP, Google-Konten); was nur der Betreiber kann (echte Angebote, Firmenname, GBP-Pin). Die zweite und dritte Sorte landen in einer `FUER-PASCAL.md` mit fertigen Blöcken und Kontrollbefehl je Block.
4. **Nichts erfinden.** Keine Rabatte, Lieferkosten, Lieferzeiten, Bewertungszahlen oder Praktiken, die nicht auf der Website, in der Preisliste oder beim Betreiber belegt sind. Was fehlt, wird als Lücke benannt („braucht Betreiberdaten"). Stammpreise sind keine Angebote.
5. **Ablage je Audit-Lauf** unter `IT Workspace/SEO & Websitehosting/<domain>-audit/<TTMMJJJJ>/`: `ABSCHLUSSBERICHT.md`, `UMSETZUNGSPLAN.md` mit Ergebnistabelle (Befund, Maßnahme, Beleg), `FUER-PASCAL.md`, bei Textarbeit `TEXTE-QA.md` mit dem Wortlaut. Nach-Fix-Läufe in `<TTMMJJJJ>-nachher/`. Nach jedem Lauf bekommt der Seed `IT Workspace/SEO & Websitehosting/_MOC-seo-websitehosting.md` einen Eintrag für diesen Lauf: Datum, Art des Laufs, gemessener Score, Link auf `ABSCHLUSSBERICHT.md`, darunter eingerückt die Links auf die übrigen Dokumente desselben Laufordners (`UMSETZUNGSPLAN.md`, `FUER-PASCAL.md`, `TEXTE-QA.md` und so weiter) ohne eigene Zusammenfassung. Den neuesten Lauf stellst du nach oben. `findings/`, Rohdaten und die Preisliste bekommen keinen Seed-Eintrag. Befunde früherer Läufe sind oft längst behoben (Punkt 1) und sollen nicht prominent verlinkt sein.
6. **`/seo audit`-Subagenten laufen in ihr 15-Turn-Limit** (Schema, Content, Visual, SXO am 07.09.). Per SendMessage an die Task-ID fortsetzen lassen, das Teilergebnis nicht als Endstand nehmen.
## 2 WPVibe-Connector
- **Vor jeder Arbeit `list_sites`** für das Kontingent: Free-Plan seit 2026-09-14 nur noch 100 Aufrufe je rollende 24 Stunden (vorher 300), die Zahl steht in der Antwort von `list_sites`. Massenänderungen als eine Anweisung, nicht als Schleife. Lesen bündeln (`db query` mit `SUBSTRING` über mehrere IDs), Live-Prüfung per curl und App-Browser statt über den Connector.
- **Barrierefreiheit messen** mit axe-core im App-Browser (per `<script>` von cdnjs nachladen, vorher einmal ganz scrollen, sonst verstecken Einblendanimationen Elemente) und einem Tab-Test von Hand; axe sieht Fokus und Tastaturbedienung nicht zuverlässig. Keine Overlay-Plugins. Den Fokusrahmen von WP Accessibility (`wpa_focus`, Standard #233c7f) nicht nehmen, wenn die Site grüne Flächen hat: 2,8:1 auf #009755.
- **Freigaben:** Link erst erzeugen, wenn der Klick sofort kommt; sie können ablaufen, bevor der Klick ankommt. Nach jeder Freigabe `check_approval_status` **und** eine Zählabfrage am Datenbestand. „executed" heißt nicht „wirkt" (Beispiel: Option gelöscht, Sitemap trotzdem alt, siehe Abschnitt 4).
- **Klassifikator-Ablehnung** (`Blocked by classifier`) ist nicht deterministisch. Einmal über den anderen Weg versuchen: REST statt WP-CLI (`PUT /wp/v2/pages/{id}` statt `post update`), `code_snippet update` statt `create`. Nicht mehrfach denselben Aufruf wiederholen.
- **Inhaltsänderungen** über `POST /wpvibe/v1/content/edit` (match-once `str_replace` auf `post_content`, der Inhalt kommt nicht in den Kontext). Den `old_content` aus dem **rohen** `post_content` nehmen (`post get <id> --fields=post_content`), nicht aus dem gerenderten HTML; Entities und `wpautop` verändern den Text. Dieser Weg löst **keine** WordPress-Hooks aus: kein `save_post`, keine Rank-Math-Invalidierung, aber `post_modified` wird gesetzt.
- **Emulator statt WP-CLI:** `help` und `help <familie>` lesen, nicht raten. `plugin delete` heißt `plugin uninstall`, `menu delete` fehlt (REST `DELETE /wp/v2/menus/{id}` mit force), Semikolons in `db query` sind verboten, `post meta update` auf `rank_math_*` braucht ab dem zweiten Mal `--force`. `option patch insert|update <option> <key> <wert>` schreibt einzelne Schlüssel in serialisierte Optionen, ohne die Option ganz zu laden.
- `content/edit` meldet bei einem Ausschnitt, der mitten in einem Gutenberg-Block beginnt, „parser found 0 blocks". Das prüft nur den Ausschnitt; belegt ist erst die Zählung von Öffnungs- und Schlusskommentaren der ganzen Seite per `db query`.
- **Theme-Dateien** sind über `search_files` und `read_file` lesbar (aktives Theme, read-only), Plugin-Dateien nicht, auch nicht mit `scope=wp-content` (HTTP 404, WPVibe 1.16.5). Ein unbekanntes Shortcode-Attribut erst auf einer Seite setzen und am gerenderten HTML prüfen. Wer Rank-Math-Verhalten wissen will, liest den Quelltext aus dem WordPress.org-SVN.
## 3 WPCode-Snippets
- Jedes PHP-Snippet braucht Freigabe und wird **deaktiviert** geschrieben; auch jedes `update` schaltet es aus. Pascal schaltet im Backend ein. Danach `post_status` des Snippet-Posts (`post_type = wpcode`) abfragen: `publish` heißt an, `draft` heißt aus. Am 07.09. war das erste Einschalten nicht angekommen; die Aussage „ist an" war falsch, die Datenbank nicht.
- Deshalb **Snippet-Änderungen bündeln**: eine Freigabe-plus-Einschalten-Runde je Sitzung, nicht je Einzeiler.
- `style_loader_tag` bekommt den Handle **ohne** `-css` (`wpa-style`, nicht `wpa-style-css`); das Suffix hängt WordPress erst im `id`-Attribut an. Rohinhalt einer Seite immer über `get_post($id)->post_content`, nie über `get_post_field()`, das Anzeigefilter durchläuft und Shortcodes verliert.
- Salient setzt `skip-lazy` auf jedes Bild und schaltet natives Lazy Loading in `class-nectar-lazy.php` fest ab. Ein Lazy-Loading-Snippet, das `skip-lazy` respektiert, tut nichts.
- **Sicherheits-Header nie per PHP-Hook** setzen: WP Super Cache liefert vor WordPress aus. Sie gehören in die `.htaccess`, **oberhalb** des WordPress- und des Super-Cache-Blocks, sonst überschreibt WordPress sie.
## 4 Rank Math (belegt mit 1.0.277.2)
- Ohne verbundenes Konto oder `rank_math_registration_skip = 1` liefert Rank Math **nichts** (kein Titel, keine Sitemap, kein Schema), obwohl das Plugin aktiv ist. Nach dem Setzen `rewrite flush`.
- Optionen: `rank-math-options-titles` (nicht `rank_math_titles`), `rank-math-options-sitemap`, `rank-math-options-instant-indexing`; Module in `rank_math_modules`; Local SEO braucht `local-seo`.
- Auf normalen Seiten lässt Rank Math bei „Schema-Typ: off" den ganzen JSON-LD-Graph weg. Filter `rank_math/schema/add_global_entities` auf `__return_true` gibt Organization/Store, WebSite und WebPage auf allen Seiten aus.
- **Der Sitemap-Cache sind Dateien**, nicht die Option: `wp-content/uploads/rank-math/rank_math_<hash>.xml`, Liste in `rank_math_sitemap_cache_files`. Rank Math prüft die Datei. Option löschen reicht nicht, `content/edit` und REST-`PUT` invalidieren nicht. Dateien per WebFTP löschen oder Sitemap-Einstellungen im Backend speichern.
- Rank Math **escaped nach** dem Filter `rank_math/json_ld`: ein `html_entity_decode` auf `hasMap` im Filter kommt als `&amp;` wieder heraus. Ein FAQPage-Objekt aus dem Filter mergt Rank Math in das WebPage-Objekt (`@type: FAQPage`, `@id …#webpage`); das ist gültig und gewollt.
- `opening_hours_format` steuert nur den Shortcode, nicht das Schema. `openingHoursSpecification` kommt über einen Filter.
- IndexNow: Modul `instant-indexing` plus Schlüsseldatei `/<key>.txt` mit HTTP 200. Vor dem Befund „nicht implementiert" die Datei abrufen.
## 5 Salient / WPBakery
- Custom CSS liegt in `salient_redux['custom-css']` (Key laut `nectar/redux-framework/options-config.php`) und wird bei jedem Aufruf inline ausgegeben; `option patch update salient_redux custom-css "…"` reicht, kein Regenerieren. Header-Icons brauchen `!important`, Salient setzt `#header-outer[data-lhe] #top nav > ul > #social-in-menu > a { padding: 0 }`.
- Seiten haben oft **zwei Überschriftenreihen** (Desktop `vc_hidden-xs vc_hidden-sm vc_hidden-md`, Mobil `vc_hidden-lg`). Ein Befund „H1 0×0 auf Mobil" heißt: die Mobilreihe trägt ein H2. Wer eine H1 ändert, ändert zwei Stellen.
- Horizontaler Scroll kam von `column_margin_custom="7vw"` einer Reihe (7vw/2 = 67 px bei 1920), nicht von der Leaflet-Karte. `scrollWidth` gegen `clientWidth` messen, nicht raten.
- `maximum-scale=5` im Viewport: die einzige Theme-Alternative ist `maximum-scale=1, user-scalable=0`, schlechter. Lassen.
- **Google-Karte (`[nectar_gmap]`, belegt mit Salient 18.2.1 / Salient Core 3.1.5):** Marker stehen im Shortcode-Attribut `map_markers="lat|lng|Info"`; ohne es rendert `map.js` einen leeren `.map-marker`. `marker_style="nectar"` zeigte keinen Pin, `default` schon. `map.js` (Handle `nectarMap`) lädt die Maps API sofort beim Seitenaufruf, ohne Salients Lazy-Script-Mechanik. Datenschutzkonform nur mit Klick-Lösung: das Klick-Snippet der Kundensite als Vorlage (Snippet-ID steht im Harness-Gedächtnis) (`script_loader_tag` → `type="text/plain"`, Overlay mit Button, Klick bindet das Skript ein). Messen mit Playwright: Anfragen an Google-Hosts vor dem Klick müssen 0 sein.
- **Wer eine Einbindung tauscht (Karte, Schriften, Tracking), fasst im selben Schritt Datenschutzerklärung und CSP an.** Am 15.09.2026 ersetzte eine Google-Karte die OSM-Karte, die Datenschutzerklärung beschrieb weiter OSM und „keine Daten an Google".
- Mobile Touch-Ziele unter 44 px im Header sind die Telefon-/E-Mail-Icons (`.button_social_group a`) und der Menü-Toggle (`.slide-out-widget-area-toggle a`); Padding plus negativer Margin hält das Layout.
## 6 Hoster und Cache
- **All-Inkl:** `exec()` aus. EWWW findet „0 Bilder", obwohl Optionen und Cron korrekt sind; Imsanity plus Converter for Media brauchen kein `exec()`. Vor der Plugin-Wahl fragen, ob es Binärdateien braucht.
- **WP Super Cache:** nach jeder Änderung `cache purge`; Optionsschreibungen über den Emulator purgen automatisch. Cache-Buster ist ein **langer erfundener** Parameter (`?agentcheck=1`), nie ein einbuchstabiger: `w` ist die Kalenderwoche, `m` das Datum, `s` die Suche, `p` die Post-ID, alle liefern eine 404-Seite mit HTTP 200. Wenn nach einer Änderung alle Seiten gleich groß sind, zuerst `<title>` und `body class` lesen.
- Cache-Header und CSP gehören in die `.htaccess` (siehe Abschnitt 3). CSP erst Report-Only, eine Woche beobachten, dann scharf.
## 7 Messregeln
- Wortzahlen nur aus `<p>` und `<li>` mit striktem Muster `<p(?:\s[^>]*)?>`, vorher `<svg>`, `<script>`, `<style>` entfernen. `<p[^>]*>` trifft `<path>` und zählt SVG-Icons als Absätze. Navigations-Kurzlabels sind kein Inhalt.
- Eine Zahl, die vom Audit oder einer zweiten Messung um mehr als ein paar Prozent abweicht, ist ein Messfehler, bis das Gegenteil belegt ist.
- JSON-LD prüfen: Skripte extrahieren, `json.loads`, dann je Seite Typen, `hasMap`, `address` und FAQ-Antwortlängen ausgeben. Rich Results Test läuft ohne Login im App-Browser; die PageSpeed-API ist nach wenigen Läufen für den Tag erschöpft (HTTP 429), Lab-Werte deshalb datieren.
- Shortcode-Lecks nach Inhaltsänderungen: `\[/?(?:toggle|vc_|nectar)[^\]]*\]` auf dem gerenderten HTML muss leer sein.
- Browser-Messung im App-Pane: `getBoundingClientRect` und die Elternkette bis zum ersten `display: none`, nicht der Screenshot; der Screenshot-Kanal rendert nicht immer.
## 8 Inhalte
- Frage-Antwort-Toggles (`[toggle title="…"]`) werden serverseitig gerendert und sind crawlbar; das FAQPage-Schema liest sie aus dem Rohinhalt. Zielkorridor je Antwort für KI-Zitierbarkeit 134–167 Wörter; 120–150 ist erreichbar, ohne zu schwafeln.
- Jede Antwort: Faustregel mit Rechenbeispiel, Preisbeispiel mit Datum und „freibleibend, inklusive Mehrwertsteuer", zuständige Person mit Link auf `/team/`. Preise nur aus der Preisliste des Betreibers, mit Stand.
- Team-Verlinkung heißt Link **im Inhalt**, nicht im Menü.
- Schreibstil nach [[AI/Regelwerk/schreibstil|schreibstil.md]]: Sie-Form wie die Site, keine Marketingfloskeln. Ausnahme vom Listenstil: FAQ-Antworten auf der Site bleiben Fließtext im Zielkorridor oben, weil Schema und KI-Zitierbarkeit ganze Antworten lesen.
## Verwandt
- [[AI/Regelwerk/schreibstil|schreibstil.md]] — Ton der Texte
- [[AI/Regelwerk/pruefwerkzeuge|pruefwerkzeuge.md]] — wenn aus einer Messregel ein Skript wird
- [[CLAUDE|↑ Claude.md (Stamm)]]