Custom Gutenberg Blocks mit Timber & Twig: So baue ich WordPress-Websites
Page Builder wie Elementor oder WPBakery sind bequem, aber sie erzeugen aufgeblähten Code, sind langsam und ein Wartungs-Albtraum. Ich setze stattdessen auf maßgeschneiderte Gutenberg-Blöcke mit PHP 8.2, Timber/Twig und einem eigenen Block-Framework. Warum, und wie der Aufbau im Einzelnen aussieht.
Von Dennis Theis aktualisiert 7 Min. Lesezeit
Das Problem mit Page Buildern
Page Builder haben WordPress demokratisiert. Jeder kann damit Seiten gestalten. Doch für
professionelle Websites haben sie gravierende Nachteile: mehrere hundert Kilobyte
JavaScript allein für den Builder, in den Installationen, die ich übernommen habe, meist
zwischen 400 und 800 KB. Dazu verschachtelte <div>-Strukturen ohne
Semantik und eine Abhängigkeit vom Plugin, die bei Updates regelmäßig für Probleme sorgt.
Die Builder-Seiten, die ich zum Optimieren bekomme, liegen beim Lighthouse-Score auf Mobile fast immer unter 70. Das ist kein Zufall, sondern strukturell: Page Builder rendern auf der Client-Seite und laden Assets, die für die einzelne Seite unnötig sind.
Mein Ansatz: Block-basiertes WordPress
Statt eines Page Builders setze ich auf den nativen WordPress Block-Editor (Gutenberg), erweitert um eigene Blöcke, die exakt das tun, was die Website braucht. Nicht mehr, nicht weniger.
Der Tech Stack:
- PHP 8.2+ mit Bedrock als WordPress-Struktur
- Timber 2 / Twig als Template-Engine (trennt Logik von Markup)
- Native Gutenberg Block API mit React-basierter Editor-UI (
edit.js) - ACF Pro nur für globale Optionen, allgemein übersetzbare Strings und Custom Post Types
- Vite als Build-Tool (Hot Module Replacement, CSS-Bundling)
- Eigenes Block-Framework mit Traits, automatischer Asset-Verwaltung und Theme-System
Anatomie eines Custom Blocks
Jeder Block besteht aus einer klaren Dateistruktur. Am Beispiel eines „MediaText"-Blocks, einer Kombination aus Bild und Text, die in verschiedenen Layouts dargestellt werden kann:
components/block/MediaText/
├── block.json # Metadaten, Attribute, Presets
├── template.php # PHP-Klasse mit Datenaufbereitung
├── index.twig # Twig-Template (HTML-Output)
├── style.css # Scoped CSS (PostCSS, @layer)
├── edit.js # React-basierte Editor-UI
└── index.js # Block-Registrierung block.json: Metadaten und Attribute
Die block.json definiert den Block für WordPress: Name, Attribute mit Typen und Defaults,
Editor-Unterstützung und sogenannte Presets, vordefinierte Konfigurationen für
Spacing und Theming. Alle Block-Daten leben hier als native Gutenberg-Attribute, nicht als ACF-Felder.
edit.js: React-basierte Editor-UI
Die Editor-Oberfläche wird komplett in React gebaut, mit den nativen Gutenberg-Komponenten
InspectorControls und PanelBody. Eigene Shared Components wie
ImageUploadPanel, VideoUploadPanel und BackgroundThemePanel
sorgen für eine konsistente Bedienung über alle Blöcke hinweg.
Der Vorteil gegenüber ACF-basierten Blöcken: Die edit.js rendert eine
Live-Vorschau des Blocks direkt im Editor. Der Redakteur sieht sofort,
wie der Block auf der Website aussehen wird, nicht nur eine Liste von Formularfeldern.
template.php: Datenaufbereitung in PHP
Die PHP-Klasse erbt von BlockComponent und nutzt Traits für
wiederkehrende Funktionalität: HasImages für Bildverarbeitung,
HasBackgroundTheme für Farbvarianten, WithComponentClasses für
dynamische CSS-Klassen. Die Block-Attribute aus block.json werden hier
für das Twig-Template aufbereitet, inklusive Schema-Daten für JSON-LD.
index.twig: Sauberes Markup mit Twig
Hier zeigt sich der größte Vorteil von Timber: Das HTML-Template ist komplett von der
PHP-Logik getrennt. Kein <?php echo ... ?> im Markup, keine verschachtelten
Conditionals. Stattdessen sauberes Twig mit Makros für wiederkehrende Elemente:
{% from 'macros/renderImage.twig' import renderImage %}
{% block content %}
<div class="media">
{{ renderImage(image, {aspectRatio: '16 / 9'}) }}
</div>
<div class="content">
<h2>{{ i18n.heading|e }}</h2>
<p>{{ i18n.text|e }}</p>
</div>
{% endblock %}
Der renderImage-Makro kümmert sich automatisch um srcset, sizes,
WebP/AVIF-Konvertierung, Lazy Loading und Alt-Texte. Kein Entwickler muss sich um
Bild-Optimierung kümmern. Das Framework erledigt das.
Das CSS Layer System
Anstatt CSS-Spezifitätskonflikte mit !important zu lösen, nutze ich
CSS Cascade Layers. Der Unterschied: Bei klassischem CSS entscheidet die
Spezifität des Selektors, wer gewinnt, weshalb Projekte mit der Zeit immer längere
Selektorketten ansammeln. Mit Cascade Layers entscheidet stattdessen die Reihenfolge der
Layer, unabhängig von der Spezifität. Ein Block-Style in einem späteren Layer überschreibt
einen Reset im früheren, auch wenn der Reset der spezifischere Selektor wäre. Damit
verschwindet der häufigste Grund für !important:
@layer reset, base, themes, vendor, layout, components, utilities
Jede Ebene hat eine definierte Priorität. Block-CSS lebt in @layer components
und kann nie versehentlich Reset- oder Theme-Styles überschreiben. Das Ergebnis:
keine Spezifitätskonflikte, kein !important, vorhersagbare Kaskade.
Die kritischen Layer (reset, base, themes, layout,
utilities) werden direkt im <head> als Inline-CSS ausgeliefert.
Kein render-blockierender CSS-Download für den initialen Paint.
Theme-System: 5 Varianten, null Komplexität
Jeder Block unterstützt automatisch fünf Farbvarianten: Standard, Light, Dark, Grey und Reset. Der Redakteur wählt im Editor eine Variante, und der Block passt sich über CSS Custom Properties an:
<devslab-block name="MediaText" data-theme="dark">
<!-- Alle Farbtokens werden automatisch überschrieben -->
</devslab-block>
Das data-theme-Attribut überschreibt ~20 CSS-Variablen: Hintergrund, Text, Buttons,
Borders. Kein Block-CSS muss Theme-spezifische Styles definieren. Das Theme-System erledigt das global.
Lazy Script Loading: JavaScript nur wenn nötig
Nicht jeder Block braucht JavaScript. Und wenn doch, dann erst wenn der Block sichtbar wird.
Mein Framework nutzt ein load:on-Attribut mit vier Strategien:
visible(Standard): Script lädt, wenn der Block in den Viewport scrolltload: Sofort beim Seitenaufruf (nur für Above-the-Fold)idle: Wenn der Browser nichts zu tun hatinteraction: Erst bei Klick oder Touch (für schwere Libraries)
Das Ergebnis: Eine typische Seite mit 8 Blöcken lädt initial nur 1–2 Scripts. Der Rest wird nachgeladen, wenn der Nutzer scrollt. Das verbessert den INP-Wert drastisch.
Warum native Gutenberg-Attribute statt ACF-Blöcke?
Viele Entwickler nutzen ACF Pro, um Gutenberg-Blöcke zu erstellen, mit acf_register_block_type()
und ACF-Feldern als Datenquelle. Das funktioniert, hat aber Nachteile gegenüber nativen
Gutenberg-Attributen mit einer eigenen edit.js:
- Live-Vorschau: Native Blöcke rendern eine React-basierte Vorschau direkt im Editor. ACF-Blöcke zeigen entweder PHP-gerendertes HTML (langsam, kein Echtzeit-Feedback) oder eine Feldliste
- Unabhängigkeit: Die Block-Daten leben in
block.json, kein ACF-Plugin-Lock-in für die Block-Struktur. ACF bleibt für das, wofür es gebaut wurde: globale Optionen und Custom Post Types - Shared Components: Eigene React-Panels (
ImageUploadPanel,BackgroundThemePanel) werden über alle Blöcke wiederverwendet, konsistenter als individuelle ACF-Feldgruppen pro Block - Performance: Block-CSS wird pro Block geladen, nicht global. Keine ACF-spezifischen Assets im Frontend
- Zukunftssicherheit: Die Block API ist WordPress-Core. ACF-Block-Registration ist ein Plugin-Feature, das bei API-Änderungen brechen kann
ACF Pro ist trotzdem Teil meines Stacks, aber gezielt eingesetzt: Translatable Options für mehrsprachige Strings und globale Konfiguration, Options Pages für Seitenübergreifende Einstellungen, und Feldgruppen auf CPTs mit festem Layout (z.B. Team-Mitglieder, Referenzen). Die Block-Inhalte selbst kommen über native Gutenberg-Attribute.
Wie sich das für Redakteure anfühlt
Der technische Vergleich ist die eine Hälfte. Die andere ist die Frage, ob damit täglich gearbeitet werden kann. Eine der Agenturen, für die ich seit Jahren entwickle, pflegt ihre Projekte auf genau diesem Setup, und die Rückmeldung fällt konsistent in drei Punkte.
Der Umstieg von Elementor ist zunächst ungewohnt. Das ist die ehrliche Kehrseite: Wer es gewohnt ist, Elemente frei auf eine Seite zu ziehen, findet sich in einer Struktur wieder, die bewusst Grenzen setzt. Diese Umstellung dauert ein bis zwei Sitzungen.
Danach ist die Orientierung besser, nicht schlechter. Weil jeder Block dieselbe Logik hat, dieselben Panels und dieselben Bezeichnungen, ist nach kurzer Einarbeitung klar, wo was liegt. Ein Page Builder bietet mehr Möglichkeiten pro Element, aber jede Seite kann anders aufgebaut sein. Ein Blocksystem bietet weniger Möglichkeiten und dafür Wiedererkennbarkeit.
Änderungen gehen schnell, auch ungewöhnliche. Inhaltliche Anpassungen erledigen die Redakteure selbst, in der Regel in Minuten. Und wenn ein Projekt etwas braucht, das kein Block abbildet, ist das kein Fall für ein weiteres Plugin, sondern für einen neuen Block. Genau das ist der Unterschied zwischen eigenem Code und einem Produkt: Die Grenze verläuft nicht dort, wo der Hersteller sie gezogen hat.
Was bedeutet das für die Wartung?
Custom Blocks sind stabiler als Page Builder, aber sie brauchen trotzdem Pflege. WordPress-Core-Updates, PHP-Updates und Gutenberg-Updates können Breaking Changes mitbringen. Deshalb gehört ein professioneller Wartungsservice zu jedem Projekt: Updates werden auf einer Staging-Umgebung getestet, bevor sie live gehen.
Ein aktuelles Beispiel dafür ist der erzwungene iframed Editor in
WordPress 7.1. Er betrifft jeden Block, dessen Styles
über admin_enqueue_scripts statt über enqueue_block_assets
eingebunden werden, und fällt im Frontend zunächst gar nicht auf.
Für Agenturen, die diese Expertise nicht inhouse haben, biete ich langfristige Entwicklungspartnerschaften an. Du lieferst Design und Kundenbeziehung, ich liefere die technische Umsetzung mit Custom Blocks und Timber/Twig.
Dennis Theis
Freiberuflicher Webentwickler aus Berlin. Schwerpunkt sind Websites, die an die Systeme angebunden sind, in denen die Daten schon liegen.
Mehr über meine ArbeitsweiseWeiterlesen
- Core Web Vitals optimieren: LCP, INP und CLS LCP unter 2,5 s, INP unter 200 ms, CLS unter 0,1.
- WordPress 7.1 Update: Breaking Changes WordPress 7.1 erzwingt den iframed Editor und ändert die Hook-ID-Generierung.
- 10 Gutenberg-Tipps für schnelleres Arbeiten Tastenkürzel, Block-Patterns, wiederverwendbare Blöcke und versteckte Funktionen: 10…
Custom WordPress-Entwicklung gesucht?
Ich entwickle individuelle Gutenberg-Blöcke mit Timber, Twig und nativer Block API, performant, wartbar und exakt auf deine Anforderungen zugeschnitten.
Anfrage ist da.
Ich melde mich innerhalb eines Werktags. Schau sicherheitshalber auch in den Spam-Ordner.