Schluss mit WordPress-Frust: Mein Umzug zum statischen CMS Publii

Jahrelang war WordPress der unangefochtene Standard für Websites. Doch was einst als einfaches Blogging-System begann, hat sich zu einem trägen Monster entwickelt. Nach unzähligen Stunden voller Wartungsarbeiten, gehackten Logins und Datenbank-Säuberungen reifte der Entschluss: Ein Schnitt muss her. Die Wahl für shizoworld.de fiel auf Publii – einen modernen Static Site Generator mit lokaler Desktop-Oberfläche. Hier ist die ungeschminkte Geschichte meines Umzugs, der Gründe dafür, des KI-gestützten Migrations-Workflows und aller technischen Stolpersteine.
1. Warum WordPress einfach nur noch nervt
Die Liste der Gründe, WordPress den Rücken zu kehren, wurde von Monat zu Monat länger. Drei Hauptprobleme haben schließlich das Fass zum Überlaufen gebracht:
Dauerhafte Hack-Versuche & Sicherheits-Stress
Eine dynamische WordPress-Instanz steht unter Dauerfeuer. Automatisierte Botnetze versuchen rund um die Uhr, über die wp-login.php oder XML-RPC-Schnittstellen einzudringen. Wer hier nicht ständig schwere Sicherheits-Plugins (wie Wordfence) mitfahren lässt, läuft Gefahr, gehackt zu werden. Doch diese Plugins fressen ihrerseits enorme Server-Ressourcen.
Das "Update-Lotto" mit Überraschungseffekt
Ein Klick auf "Plugins aktualisieren" glich zuletzt einem Risikospiel. Statt stabiler Performance gab es nach Updates regelmäßig weiße Bildschirme oder zerschossene Layouts.
Die Datenbank als wartungsintensive Müllhalde
Bei dynamischen CMS-Systemen ist die Datenbank das Nadelöhr. Zwei konkrete Plugin-Beispiele der jüngsten Vergangenheit verdeutlichen das Problem:
- Internal Link Juicer: Das Plugin hat die
wp_options-Tabelle unbemerkt mit hunderten Megabyte an temporären Daten (Transienten) geflutet. Die Datenbank wurde träge, Blöcke blockierten sich gegenseitig und am Ende verweigerte das Plugin komplett den Dienst – der interne Link-Index ließ sich schlicht nicht mehr neu aufbauen. - Rank Math: Nach einem Update verlor das SEO-Plugin plötzlich die Verbindung zu seinen eigenen Cloud-Diensten. Kein Re-Connect, keine Fehlermeldung mit Nährwert – die Schnittstelle verweigerte stur die erneute Verknüpfung.
2. Publii im Detail: Die Vor- und Nachteile
Publii geht einen völlig anderen Weg: Das CMS läuft lokal als Programm auf dem Computer (Windows/Mac/Linux). Erst wenn eine Seite fertig bearbeitet ist, generiert Publii daraus reine, statische HTML-, CSS- und Medien-Dateien und lädt diese per FTP/SFTP auf den Server.
Die Vorteile von Publii
- Keine Datenbank: Wo keine Datenbank existiert, gibt es keine verstopften Options-Tabellen, keine SQL-Injections und keine Server-Latenzen.
- Maximale Sicherheit: Auf dem Webserver liegen nur fertige Dateien. Es gibt kein PHP-Backend, das gehackt werden könnte.
- Irrsinnige Ladezeiten: Statische HTML-Dateien werden vom Webserver ohne Server-Berechnungen direkt an den Besucher ausgeliefert.
- Volle lokale Kontrolle: Alle Daten liegen lokal auf dem eigenen Rechner. Ein Backup ist so einfach wie das Kopieren eines Ordners.
Die Nachteile von Publii
- Keine dynamischen Funktionen ab Werk: Für Suchfunktionen, Kommentare oder Kontaktformulare müssen externe Dienste eingebunden werden.
- Generierungszeit: Nach jeder Äderung muss die Seite lokal gerendert ("Render Website") und hochgeladen werden.
- Kein schnelles Editieren von unterwegs: Ein kurzer Beitrag per Smartphone erfordert ein aufwendiges Cloud-Sync-Setup der lokalen Daten.
3. Der Schritt-für-Schritt Migration-Workflow (inkl. KI-Bild-SEO)
Um hunderte Beiträge und Seiten verlustfrei zu übertragen und gleichzeitig Altlasten zu bereinigen, kam ein strukturierter Workflow zum Einsatz:
Schritt 1: WordPress-Export & Import in Publii
- In WordPress unter Werkzeuge → Daten exportieren die XML-Datei mit allen Beiträgen, Seiten und Kategorien herunterladen.
- In Publii über die integrierte Import-Funktion die XML-Datei einlesen. Publii konvertiert die WP-Inhalte automatisch in saubere Markdown-/HTML-Blöcke.
- Slugs prüfen: Damit Suchmaschinen-Rankings und Verlinkungen nicht abreißen, wurden die Permalinks (URL-Pfade) in Publii exakt wie in WordPress angelegt.
Schritt 2: KI-gestützte Bild-SEO & automatisierte Umbenennung
Ein riesiges Problem alter WordPress-Mediatheken sind kryptische Dateinamen wie IMG_4912.jpg oder Bildschirmfoto_2023.png. Für die Migration wurde daher eine KI-gestützte Pipeline genutzt:
- Analyse per Vision-KI: Vor dem Upload in Publii wurden die Bilder durch ein KI-Modell analysiert, das den eigentlichen Inhalt des Bildes erkennt.
- Generierung von Dateinamen & Alt-Texten: Die KI erstellte prägnante, keyword-optimierte Dateinamen mit Bindestrichen (z. B.
publii-htaccess-security-headers.webpstattscreencapture-102.jpg) sowie passendealt-Attribute für Barrierefreiheit und Bild-SEO. - WebP-Konvertierung: Vor dem Einbinden wurden alle Bilder ins moderne WebP-Format konvertiert, was die Dateigröße um bis zu 80 % reduzierte.
4. Die Suche-Hürde im Theme "Simple"
Beim kostenlosen Publii-Theme Simple folgte nach dem Upload die nächste Hürde: Die Suchleiste im Header ist zwar vorhanden, reagiert aber ohne Datenbank nicht.
Die Lösung: Integration der Google Custom Search Engine (CSE).
- Kostenlose Suchmaschine auf
cse.google.comfürshizoworld.deanlegen und die Search Engine ID kopieren. - In Publii unter Tools & Plugins das Plugin Google Custom Search aktivieren und die ID eintragen.
- Nach dem Rendern verknüpft Publii die Suchbox des Themes automatisch mit dem Google-Skript.
5. Die .htaccess-Falle: Serverfehler und CSP-Blocking
Beim Aktivieren auf dem Webserver (Strato) gab es mit der .htaccess-Datei entscheidende Feinheiten zu beachten:
- WordPress-Ballast entsorgen: Sämtliche alten
RewriteRule-Zeilen und Wordfence-Sperren wurden gestrichen, da statische Seiten keine PHP-Umleitungen benötigen. - Syntax-Falle geschützte Leerzeichen (
nbsp): Beim Kopieren von Code-Snippets schlichen sich unsichtbare Sonderzeichen ein. Apache-Server quittieren diese mit einem 500 Internal Server Error. Erst das Ersetzen durch Standard-Leerzeichen behob das Problem. - Content Security Policy (CSP) feintunen: Für externe Embeds (YouTube, Spotify, Bandcamp, Soundcloud) und die Google-Suche mussten die Domains in der CSP freigegeben werden. Fehlt beispielsweise
*.gstatic.comunterstyle-src, blockiert der Browser die Stylesheets der Google-Suchleiste.
Für das Caching gilt: Medien und Fonts erhalten ein langes Ablaufdatum (1 year, immutable), während HTML- und JSON-Dateien (Suchindex) mit no-cache belegt werden, damit neue Beiträge sofort sichtbar sind.
Fazit
Der Abschied von WordPress fühlte sich wie ein echter Befreiungsschlag an. Dank der KI-gestützten Bild-Optimierung ist die neue Mediathek heute sauberer als je zuvor. Wer die initiale Einrichtungsphase und die Verknüpfung externer Dienste (wie der Google-Suche) einmal gemeistert hat, erhält mit Publii eine pfeilschnelle, unkaputtbare Website – ganz ohne verstopfte Datenbanken, nervigen Plugin-Frust und die ständige Angst vor dem nächsten Security-Patchday.
Beim Umstieg von WordPress zu einer statischen Website mit Publii bleiben oft alte Gutenberg-Kommentare und veraltete Bildpfade in der SQLite-Datenbank zurück. Diese Schritt-für-Schritt-Anleitung zeigt, wie du die Datenbank von Altlasten befreist und gleichzeitig fehlende SEO-Metadaten automatisiert generierst.
1. Datenbank-Architektur von Publii
Publii nutzt eine schlanke SQLite-Datenbank (db.sqlite). Für Beiträge sind im Wesentlichen zwei Tabellen relevant:
posts: Enthält die Hauptdaten wie ID, Titel, Slug und den eigentlichen Artikeltext (text).posts_additional_data: Verwaltet erweiterte Einstellungen und SEO-Daten. Diese werden als JSON-Objekt im Feldvalueunter dem Schlüsselkey = '_core'gespeichert.
2. WordPress-Gutenberg-Blocks & Altlasten entfernen
WordPress speichert Block-Informationen als HTML-Kommentare (z. B. <!-- wp:paragraph -->). Zudem verweisen alte Bilder meist noch auf Verzeichnisse wie /wp-content/uploads/.
Gutenberg-Kommentare entfernen
Mit folgendem Regulären Ausdruck (RegEx) lassen sich die Block-Tags aus dem Beitragsfeld posts.text herausfiltern:
import re
# Entfernt alle Gutenberg-Block-Kommentare
cleaned_text = re.sub(r'<!--\s*/?wp:[^>]*-->', '', raw_text)Medienpfade anpassen
Alte absolute WordPress-Bildpfade (https://domain.de/wp-content/uploads/...) werden entweder durch relative Pfade oder die interne Medienstruktur von Publii (media/posts/ID/...) ersetzt.
3. SEO-Metadaten automatisch generieren
Damit Suchmaschinen die Inhalte optimal erfassen, liest Publii SEO-Titel und Meta-Beschreibungen aus dem JSON-Objekt der Tabelle posts_additional_data aus. Der entscheidende Schlüssel für die Meta-Beschreibung lautet hierbei metaDesc (nicht metaDescription).
Das folgende Python-Skript erstellt ein Backup, liest den Text jedes Beitrags aus, bereinigt ihn von HTML-Tags, kürzt ihn sauber am letzten Wort auf 155 Zeichen und schreibt das Ergebnis zurück in das JSON-Feld:
import sqlite3
import json
import re
import html
import shutil
import datetime
db_path = r'C:\Pfad\zu\deiner\Publii\input\db.sqlite'
# 1. Automatisches Sicherheits-Backup
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
shutil.copyfile(db_path, f'{db_path}.backup_{timestamp}')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 2. Beiträge verarbeiten
cursor.execute("SELECT id, title, text FROM posts")
posts = cursor.fetchall()
for post_id, title, text in posts:
# HTML-Tags entfernen & Text säubern
clean_text = re.sub(r'<[^>]+>', ' ', text or '')
clean_text = re.sub(r'\s+', ' ', html.unescape(clean_text)).strip()
# Auf max. 155 Zeichen kürzen
if len(clean_text) > 155:
truncated = clean_text[:152]
meta_desc = truncated.rsplit(' ', 1)[0] + '...' if ' ' in truncated else truncated
else:
meta_desc = clean_text or title
# JSON-Struktur abrufen / erstellen
cursor.execute("SELECT value FROM posts_additional_data WHERE post_id = ? AND key = '_core'", (post_id,))
row = cursor.fetchone()
core_data = json.loads(row[0]) if (row and row[0]) else {}
# SEO-Keys setzen (Wichtig: "metaDesc")
core_data['metaTitle'] = title.strip() if title else ''
core_data['metaDesc'] = meta_desc
json_val = json.dumps(core_data, ensure_ascii=False)
if row:
cursor.execute("UPDATE posts_additional_data SET value = ? WHERE post_id = ? AND key = '_core'", (json_val, post_id))
else:
cursor.execute("INSERT INTO posts_additional_data (post_id, key, value) VALUES (?, '_core', ?)", (post_id, json_val))
conn.commit()
conn.close()4. Wichtige Fallstricke & Best Practices
Achtung: Bevor Datenbanänderungen durchgeführt werden, müssen sowohl die Publii-App als auch etwaige SQLite-Viewer (wie der DB Browser) vollständig geschlossen sein.
- Prozess-Sperre: SQLite-Datenbanken werden bei Schreibzugriffen für andere Programme gesperrt. Schließe Publii vor der Ausführung von Skripten.
- Arbeitsspeicher-Cache: Publii lädt die Datenbank beim Programmstart in den RAM. Wenn Änderungen bei geöffnetem Publii durchgeführt werden, können sie beim Speichern innerhalb der App überschrieben werden.
- Site Rendern: Nach der Datenbankbereinigung muss in Publii einmalig der Button „Render site“ geklickt werden. Erst dadurch schreibt Publii die neuen Werte aus der Datenbank in den
<head>-Bereich der generierten HTML-Dateien.



