Im Frontend stehen Entwickler jedoch regelmäßig vor der Herausforderung, diese dynamischen Zeilen gestalterisch zu variieren. Häufig verlangt das Screendesign ein sogenanntes Zick-Zack-Muster (Bild links, Text rechts im ersten Block; Bild rechts, Text links im zweiten Block) oder wechselnde Hintergrundfarben zur visuellen Rhythmisierung.
In dieser technischen Anleitung zeigen wir Ihnen, wie Sie die have_rows()-Schleife sauber strukturieren, wie Sie den Modulo-Operator in PHP und moderne CSS-Pseudoklassen :nth-child() gezielt einsetzen und wie Sie teure Performance-Fallen in der WordPress-Datenbank vermeiden.
Das Datenmodell hinter dem ACF Repeater in der WordPress-Datenbank
Um sauberen und performanten Code zu schreiben, müssen Entwickler verstehen, wie WordPress und ACF die Werte auf Datenbankebene ablegen. WordPress speichert Beitragsmetadaten in der relationalen Tabelle wp_postmeta als einfache Schlüssel-Wert-Paare.
Wenn Sie ein Repeater-Feld mit dem Bezeichner leistungs_merkmale anlegen, das zwei Unterfelder (titel und beschreibung) enthält, und der Redakteur 4 Zeilen speichert, erzeugt ACF folgende Einträge:
- Einen Zähler-Datensatz:
leistungs_merkmalemit dem Wert4. - Einträge für Zeile 0:
leistungs_merkmale_0_titelundleistungs_merkmale_0_beschreibung. - Einträge für Zeile 1:
leistungs_merkmale_1_titelundleistungs_merkmale_1_beschreibung. - …sowie interne Referenzschlüssel für die Felddefinitionen (
_leistungs_merkmale_0_titel=field_65a12b8).
Für 10 Zeilen mit jeweils 3 Unterfeldern entstehen somit über 60 Zeilen in der Tabelle wp_postmeta. Bei unbedachter Nutzung in Schleifen kann dies zu spürbaren Verzögerungen führen, wenn WordPress viele Datenbankabfragen nacheinander absetzt.
Wie funktioniert die ACF-Schleife mit have_rows und the_row
Die empfohlene Methode zur Ausgabe eines Repeater-Felds nutzt die Funktionen have_rows() und the_row():
<?php if ( have_rows('leistungs_merkmale') ) : ?>
<section class="leistungen-repeater-wrapper" aria-label="<?php esc_attr_e('Unsere Leistungsmerkmale', 'wppoland'); ?>">
<?php while ( have_rows('leistungs_merkmale') ) : the_row();
// Unterfelder der aktuellen Zeile abrufen
$titel = get_sub_field('titel');
$beschreibung = get_sub_field('beschreibung');
$grafik = get_sub_field('grafik');
?>
<article class="leistung-karte">
<?php if ( ! empty( $grafik ) && is_array( $grafik ) ) : ?>
<div class="leistung-bild">
<?php echo wp_get_attachment_image( (int) $grafik['id'], 'medium_large' ); ?>
</div>
<?php endif; ?>
<div class="leistung-inhalt">
<h3><?php echo esc_html( $titel ); ?></h3>
<div class="text"><?php echo wp_kses_post( $beschreibung ); ?></div>
</div>
</article>
<?php endwhile; ?>
</section>
<?php endif; ?>Die Funktion the_row() rückt den internen Zeiger weiter und richtet den Datenkontext für get_sub_field() ein. Vermeiden Sie innerhalb der Schleife zwingend den Aufruf von get_field('titel'), da dieser versucht, ein globales Metadatum des Beitrags anstelle des zeilenbezogenen Unterfelds abzurufen.
Zeilen-Styling in PHP mit get_row_index und Modulo-Logik
Wenn das Layout erfordert, dass jede zweite Zeile eine andere Hintergrundfarbe erhält, die Spaltenanordnung gespiegelt wird oder unterschiedliche CSS-Klassen vergeben werden, kommt serverseitige Logik zum Einsatz.
Funktionsweise von get_row_index in ACF
Anstatt vor der Schleife eine manuelle Zählervariable ($i = 0) zu initialisieren, stellt ACF die native Funktion get_row_index() bereit. Diese gibt die Position der aktuellen Zeile als 1-basierten Index zurück (die erste Zeile liefert 1, die zweite 2 usw.):
<?php if ( have_rows('prozess_schritte') ) : ?>
<div class="prozess-schritte-liste">
<?php while ( have_rows('prozess_schritte') ) : the_row();
$index = get_row_index();
// Prüfung auf gerade Zeilen
$ist_gerade = ( $index % 2 === 0 );
$layout_klasse = $ist_gerade ? 'layout-gespiegelt' : 'layout-standard';
$hintergrund = $ist_gerade ? 'bg-hellgrau' : 'bg-weiss';
?>
<div class="prozess-schritt <?php echo esc_attr( $layout_klasse . ' ' . $hintergrund ); ?>">
<span class="schritt-nummer"><?php echo esc_html( sprintf( '%02d', $index ) ); ?></span>
<div class="schritt-text">
<h4><?php echo esc_html( get_sub_field('titel') ); ?></h4>
<p><?php echo esc_html( get_sub_field('kurzbeschreibung') ); ?></p>
</div>
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>Jedes dritte Element im Raster hervorheben
Der PHP-Modulo-Operator (%) ermittelt den Rest einer ganzzahligen Division. Damit lassen sich vielfältige Rhythmen steuern:
- Abwechselndes Zick-Zack-Muster:
$index % 2 === 0 - Hervorhebung jeder 3. Spalte im Grid:
$index % 3 === 0 - Sonderbehandlung des ersten und letzten Elements: Abgleich mit
1bzw. der Gesamtzahl aller Zeilen.
Hier ein Beispiel zur Auszeichnung von Bestseller-Karten in einem dreispaltigen Raster:
$modulo_drei = $index % 3;
if ( $modulo_drei === 0 ) {
$karten_klasse = 'karte-hervorgehoben karte-dritte';
} elseif ( $modulo_drei === 1 ) {
$karten_klasse = 'karte-standard karte-erste';
} else {
$karten_klasse = 'karte-standard karte-zweite';
}Sichere Kennzeichnung der ersten und letzten Zeile
Um das erste Element speziell auszuzeichnen oder beim letzten Element eine störende Trennlinie zu entfernen, vergleichen Sie den Index mit der Zeilenanzahl:
<?php
$rohdaten = get_field('team_mitglieder');
$gesamtanzahl = is_array( $rohdaten ) ? count( $rohdaten ) : 0;
if ( have_rows('team_mitglieder') ) : ?>
<div class="team-raster">
<?php while ( have_rows('team_mitglieder') ) : the_row();
$aktuelle_zeile = get_row_index();
$klassen = ['team-eintrag'];
if ( $aktuelle_zeile === 1 ) {
$klassen[] = 'eintrag-erster';
}
if ( $aktuelle_zeile === $gesamtanzahl ) {
$klassen[] = 'eintrag-letzter';
}
if ( $aktuelle_zeile % 2 === 0 ) {
$klassen[] = 'zeile-gerade';
} else {
$klassen[] = 'zeile-ungerade';
}
?>
<div class="<?php echo esc_attr( implode( ' ', $klassen ) ); ?>">
<p class="name"><?php echo esc_html( get_sub_field('mitarbeiter_name') ); ?></p>
<p class="position"><?php echo esc_html( get_sub_field('mitarbeiter_position') ); ?></p>
</div>
<?php endwhile; ?>
</div>
<?php endif; ?>Unterfeld-Logik und dynamische Layout-Variationen
Das Styling muss sich nicht ausschließlich an numerischen Positionen orientieren. Wenn Redakteure im Backend aus Dropdowns verschiedene Darstellungsstile wählen können, kombiniert der Code Zeilenindizes mit Unterfeldwerten:
<?php if ( have_rows('fallstudien_abschnitte') ) : ?>
<div class="fallstudien-strom">
<?php while ( have_rows('fallstudien_abschnitte') ) : the_row();
$zeilen_index = get_row_index();
$darstellung = get_sub_field('layout_variante'); // 'vollbreite', 'split', 'kompakt'
$akzent_farbe = get_sub_field('akzent_farbe');
$wrapper_klassen = [
'abschnitt-karte',
'zeile-' . $zeilen_index,
'variante-' . sanitize_html_class( $darstellung ?: 'standard' )
];
if ( $zeilen_index % 2 === 0 ) {
$wrapper_klassen[] = 'spalten-gespiegelt';
}
?>
<section
class="<?php echo esc_attr( implode( ' ', $wrapper_klassen ) ); ?>"
style="<?php echo $akzent_farbe ? esc_attr( '--zeilen-akzent: ' . sanitize_hex_color( $akzent_farbe ) . ';' ) : ''; ?>"
>
<?php
if ( $darstellung === 'vollbreite' && $zeilen_index === 1 ) {
get_template_part( 'template-parts/fallstudien/hero' );
} else {
get_template_part( 'template-parts/fallstudien/karte' );
}
?>
</section>
<?php endwhile; ?>
</div>
<?php endif; ?>Durch die Übergabe von CSS-Variablen (--zeilen-akzent) und sanitized Klassen bleibt der HTML-Code flexibel und von starren Vorgaben entkoppelt.
Wann CSS nth-child anstelle von PHP verwendet werden sollte
Nach dem Grundsatz sauberer Software-Architektur sollte Präsentationslogik im Stylesheet verbleiben, solange keine semantischen HTML-Änderungen vorliegen. Wenn lediglich Farben, Abstände oder Flexbox-Reihenfolgen variiert werden, ist CSS :nth-child() performanter und wartungsfreundlicher:
/* Zebra-Streifen für abwechselnde Zeilen */
.prozess-schritte-liste .prozess-schritt:nth-child(even) {
background-color: #f8fafc;
}
/* Umkehrung der Flexbox-Richtung in geraden Blöcken */
.prozess-schritte-liste .prozess-schritt {
display: flex;
flex-direction: row;
gap: 2rem;
align-items: center;
}
.prozess-schritte-liste .prozess-schritt:nth-child(even) {
flex-direction: row-reverse;
}
/* Hervorhebung jeder dritten Karte im CSS-Grid */
.leistungen-repeater-wrapper .leistung-karte:nth-child(3n) {
border: 2px solid #2563eb;
transform: scale(1.02);
}
/* Erstes und letztes Element */
.prozess-schritte-liste .prozess-schritt:first-child {
border-top: 3px solid #0f172a;
}
.prozess-schritte-liste .prozess-schritt:last-child {
border-bottom: 3px solid #0f172a;
}Entscheidungsmatrix: PHP oder CSS
- CSS
:nth-child(): Reine Farbänderungen, Typografieanpassungen, Randgestaltungen, responsive Flexbox-Umkehrung via Media Queries. - PHP Modulo / Zähler: Unterschiedliche HTML-Elemente (
<aside>statt<section>), Laden separater Template-Dateien viaget_template_part(), Ausgabe von Schema.org JSON-LD Positionsmetadaten.
Integration mit ACF Flexible Content für modulare Seitenstrukturen
Wenn wiederholte Abschnitte völlig unterschiedliche Feldstrukturen erfordern, führt ein überladenes Repeater-Feld zu unübersichtlichen Eingabemasken. Das Flexible Content Feld stellt hierfür das passende Architekturmuster dar:
<?php if ( have_rows('seiten_bausteine') ) : ?>
<main class="bausteine-container">
<?php while ( have_rows('seiten_bausteine') ) : the_row();
$layout_typ = get_row_layout();
$index = get_row_index();
?>
<section class="baustein baustein-<?php echo esc_attr( $layout_typ ); ?>" data-position="<?php echo (int) $index; ?>">
<?php
if ( $layout_typ === 'hero_buehne' ) :
get_template_part( 'template-parts/module/hero', null, [ 'index' => $index ] );
elseif ( $layout_typ === 'vorteile_raster' ) :
get_template_part( 'template-parts/module/vorteile', null, [ 'index' => $index ] );
elseif ( $layout_typ === 'kunden_meinungen' ) :
get_template_part( 'template-parts/module/testimonials', null, [ 'index' => $index ] );
endif;
?>
</section>
<?php endwhile; ?>
</main>
<?php endif; ?>Die Funktion get_row_layout() sorgt für klare Verantwortlichkeiten im Theme und verhindert ungenutzte Datenbankeinträge für irrelevante Unterfelder.
Gutenberg ACF Blöcke mit InnerBlocks und Block Context
In modernen Block-Themes ersetzen ACF-Gutenberg-Blöcke starre Backend-Metaboxen. Anstelle monolithischer Repeater-Strukturen setzt modernes WordPress-Engineering auf Container-Blöcke mit InnerBlocks und Block Context.
Block-Registrierung via block.json
In der block.json des Kind-Blocks wird die Eltern-Kind-Beziehung sowie der Kontext deklariert:
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "wppoland/prozess-karte",
"title": "Prozess Karte",
"category": "design",
"parent": [ "wppoland/prozess-container" ],
"usesContext": [ "wppoland/rasterLayout" ],
"acf": {
"mode": "preview",
"renderTemplate": "render.php"
}
}Container- und Item-Rendering
Im Rendering-Template des übergeordneten Blocks (prozess-container/render.php) binden wir den InnerBlocks-Bereich ein:
<div class="prozess-container-block">
<InnerBlocks
allowedBlocks="<?php echo esc_attr( wp_json_encode( ['wppoland/prozess-karte'] ) ); ?>"
template="<?php echo esc_attr( wp_json_encode( [ ['wppoland/prozess-karte'], ['wppoland/prozess-karte'] ] ) ); ?>"
/>
</div>Im Template des Kind-Blocks (prozess-karte/render.php) greifen wir direkt auf den übergebenen Kontext zu:
<?php
$raster = $context['wppoland/rasterLayout'] ?? 'spalten-3';
$titel = get_field('schritt_titel');
$text = get_field('schritt_beschreibung');
?>
<div class="prozess-karte-item layout-<?php echo esc_attr( $raster ); ?>">
<h4><?php echo esc_html( $titel ); ?></h4>
<div><?php echo wp_kses_post( $text ); ?></div>
</div>Diese Architektur ermöglicht es Redakteuren, einzelne Karten visuell im Gutenberg-Editor per Drag-and-Drop neu anzuordnen und individuell zu formatieren.
Performance-Optimierung und Datenbankstrategien bei großen Repeater-Feldern
Tief verschachtelte Repeater-Felder belasten die Tabelle wp_postmeta erheblich. Enthält ein äußerer Repeater 15 Zeilen und jede Zeile einen inneren Repeater mit 10 Einträgen à 3 Unterfeldern, speichert WordPress über 500 Metadatensätze für einen einzigen Beitrag.
1. Datenabruf als Array via get_field
Anstelle der standardmäßigen while( have_rows() )-Schleife, die bei jedem Durchlauf interne Pointer aktualisiert, ist bei sehr großen Datenmengen das einmalige Auslesen als PHP-Array performanter:
$alle_schritte = get_field('prozess_schritte');
if ( ! empty( $alle_schritte ) && is_array( $alle_schritte ) ) {
echo '<div class="direkt-array-liste">';
foreach ( $alle_schritte as $null_index => $zeile ) {
$nummer = $null_index + 1;
$klasse = ( $nummer % 2 === 0 ) ? 'eintrag-gerade' : 'eintrag-ungerade';
$titel = $zeile['titel'] ?? '';
$text = $zeile['kurzbeschreibung'] ?? '';
echo '<div class="' . esc_attr( $klasse ) . '">';
echo '<h4>' . esc_html( $titel ) . '</h4>';
echo '<p>' . esc_html( $text ) . '</p>';
echo '</div>';
}
echo '</div>';
}2. Caching des fertigen HTML mit der Transients API
Für aufwendige Sektionen, die sich nur selten ändern, empfiehlt sich das Zwischenspeichern des fertig gerenderten HTML-Codes:
function wppoland_get_cached_leistungen( int $post_id ): string {
$cache_key = 'wppoland_leistungen_html_' . $post_id;
$html = get_transient( $cache_key );
if ( false !== $html ) {
return $html;
}
if ( ! have_rows('leistungs_merkmale', $post_id) ) {
return '';
}
ob_start();
echo '<div class="leistungen-grid">';
while ( have_rows('leistungs_merkmale', $post_id) ) {
the_row();
$i = get_row_index();
$klasse = ( $i % 2 === 0 ) ? 'karte-gerade' : 'karte-ungerade';
echo '<div class="' . esc_attr( $klasse ) . '">';
echo '<h3>' . esc_html( get_sub_field('titel') ) . '</h3>';
echo '<div>' . wp_kses_post( get_sub_field('beschreibung') ) . '</div>';
echo '</div>';
}
echo '</div>';
$html = ob_get_clean();
// Cache für 24 Stunden (86400 Sekunden) anlegen
set_transient( $cache_key, $html, 86400 );
return $html;
}
// Cache-Invalidierung bei Beitragsaktualisierung
add_action('save_post', function( int $post_id ): void {
if ( ! wp_is_post_revision( $post_id ) && ! wp_is_post_autosave( $post_id ) ) {
delete_transient( 'wppoland_leistungen_html_' . $post_id );
}
});Dadurch reduziert sich die Ladezeit umfangreicher Seiten auf wenige Millisekunden.
REST API und Headless WordPress Optimierung von Repeater-Daten
In Headless-WordPress-Architekturen mit Frameworks wie Next.js oder Astro werden ACF-Repeater-Daten über die WP REST API ausgeliefert. Standardmäßig enthält die API-Antwort unformatierte Rohdaten. Durch register_rest_field lässt sich die Antwort serverseitig schlank und konsumierbar aufbereiten:
add_action('rest_api_init', function(): void {
register_rest_field('page', 'leistungen_optimiert', [
'get_callback' => function( array $post_arr ): array {
$post_id = $post_arr['id'];
$zeilen = get_field('leistungs_merkmale', $post_id);
if ( empty( $zeilen ) || ! is_array( $zeilen ) ) {
return [];
}
return array_map( function( array $zeile, int $key ): array {
return [
'index' => $key + 1,
'istGerade' => ( ( $key + 1 ) % 2 === 0 ),
'titel' => sanitize_text_field( $zeile['titel'] ?? '' ),
'text' => wp_kses_post( $zeile['beschreibung'] ?? '' ),
];
}, $zeilen, array_keys( $zeilen ) );
},
'schema' => [
'description' => 'Optimierte Leistungsmerkmale für Headless Frontend.',
'type' => 'array',
],
]);
});Fehlersuche und typische Stolperfallen in Repeater-Schleifen
Bei der Implementierung von ACF-Repeater-Feldern treten regelmäßig typische Entwicklerfehler auf:
- Endlosschleifen durch fehlendes the_row(): Wenn der Aufruf von
the_row()in derwhile-Bedingung oder direkt danach vergessen wird, verbleibt der Schleifenzeiger dauerhaft auf der ersten Zeile, was das Skript zum Absturz bringt. - Leere Ausgaben durch get_field() anstelle von get_sub_field(): Der Aufruf von
get_field()innerhalb einer Repeater-Zeile sucht im übergeordneten Beitrag nach dem Feldnamen und schlägt fehl. - Typ-Fehler bei count() in PHP 8: Wenn vor
count( get_field('mein_repeater') )nicht geprüft wird, ob der Rückgabewert ein Array ist, bricht PHP 8 mit einem TypeError ab. - CSS nth-child greift nicht wegen Slider-Wrapper: Wenn JavaScript-Bibliotheken (z. B. Swiper) zusätzliche Hilfs-Divs erzeugen, passen die CSS-Selektoren
:nth-child()nicht mehr zu den logischen Kindelementen. In solchen Fällen ist die Vergabe von PHP-Klassen die sicherere Wahl.
Komponentenbasierte Theme-Architektur mit get_template_part und Argumenten
In modernen Enterprise-Themes wird Vermischung von Datenbanklogik und HTML-Auszeichnung in einer einzigen Datei vermieden. Stattdessen wird die Ausgabe einzelner Zeilen in wiederverwendbare Komponenten (Template Parts) ausgelagert. Seit WordPress 5.5 unterstützt get_template_part() ein drittes Argument $args, über das strukturierte Daten typsicher an die Teil-Vorlage übergeben werden.
<?php if ( have_rows('leistung_elemente') ) : ?>
<div class="leistungen-raster-grid">
<?php while ( have_rows('leistung_elemente') ) : the_row();
$zeilen_nummer = get_row_index();
$karten_daten = [
'index' => $zeilen_nummer,
'ist_gerade' => ( $zeilen_nummer % 2 === 0 ),
'titel' => get_sub_field('element_titel'),
'text' => get_sub_field('element_beschreibung'),
'icon' => get_sub_field('element_icon'),
];
get_template_part( 'template-parts/komponenten/leistung-karte', null, $karten_daten );
?>
<?php endwhile; ?>
</div>
<?php endif; ?>In der Komponentendatei template-parts/komponenten/leistung-karte.php greifen Sie sauber auf das $args-Array zu:
<?php
/**
* Teilvorlage fuer einzelne Leistungskarte.
*
* @var array $args Uebergebene Zeilendaten.
*/
$index = (int) ( $args['index'] ?? 1 );
$ist_gerade = (bool) ( $args['ist_gerade'] ?? false );
$titel = $args['titel'] ?? '';
$text = $args['text'] ?? '';
$icon = $args['icon'] ?? null;
$karten_klasse = $ist_gerade ? 'leistung-element stil-gerade' : 'leistung-element stil-ungerade';
?>
<article class="<?php echo esc_attr( $karten_klasse ); ?>" data-position="<?php echo esc_attr( $index ); ?>">
<?php if ( ! empty( $icon['id'] ) ) : ?>
<div class="element-icon">
<?php echo wp_get_attachment_image( (int) $icon['id'], 'thumbnail' ); ?>
</div>
<?php endif; ?>
<h3 class="element-titel"><?php echo esc_html( $titel ); ?></h3>
<div class="element-text"><?php echo wp_kses_post( $text ); ?></div>
</article>Durch diese Trennung kann das Erscheinungsbild der Karte unabhängig vom Kontext getestet und überarbeitet werden, ohne dass Anpassungen an der umschließenden Schleife vorgenommen werden müssen.
Verschachtelte Repeater-Felder sauber und fehlersicher implementieren
Häufig erfordern Kundenprojekte hierarchische Strukturen wie beispielsweise eine Liste von Firmenstandorten, wobei an jedem Standort mehrere Ansprechpartner aufgeführt sind. In ACF wird dies durch einen verschachtelten Repeater (Nested Repeater) abgebildet.
<?php if ( have_rows('unternehmens_standorte') ) : ?>
<div class="standorte-uebersicht">
<?php while ( have_rows('unternehmens_standorte') ) : the_row();
$standort_index = get_row_index();
$stadt_name = get_sub_field('stadt');
?>
<section class="standort-abschnitt standort-<?php echo esc_attr( $standort_index ); ?>">
<h2 class="stadt-titel"><?php echo esc_html( $stadt_name ); ?></h2>
<?php if ( have_rows('ansprechpartner') ) : ?>
<ul class="partner-liste">
<?php while ( have_rows('ansprechpartner') ) : the_row();
$partner_index = get_row_index();
$name = get_sub_field('name');
$abteilung = get_sub_field('abteilung');
$zeilen_stil = ( $partner_index % 2 === 0 ) ? 'kontakt-zeile-b' : 'kontakt-zeile-a';
?>
<li class="partner-eintrag <?php echo esc_attr( $zeilen_stil ); ?>">
<strong><?php echo esc_html( $name ); ?></strong>
<span><?php echo esc_html( $abteilung ); ?></span>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
</section>
<?php endwhile; ?>
</div>
<?php endif; ?>In diesem Kontext liefert get_row_index() innerhalb der inneren Schleife ansprechpartner die relative Position des Kontakts innerhalb des jeweiligen Standorts. So lassen sich lokale Zebra-Muster und Zähler problemlos umsetzen.
Praxislösung: Preistabelle mit flexiblen Karten und Empfehlungs-Badge
Als zusammenfassendes Praxisbeispiel implementieren wir eine vollständige Preistabelle. Sie demonstriert die Kombination von Index-Prüfung, Modulo-Logik und benutzerdefinierten Schaltern:
<?php if ( have_rows('preis_staffeln') ) :
$rohdaten = get_field('preis_staffeln');
$anzahl = is_array( $rohdaten ) ? count( $rohdaten ) : 0;
?>
<section class="preise-sektion" id="preise" aria-label="<?php esc_attr_e('Preistabelle', 'wppoland'); ?>">
<div class="preis-raster">
<?php while ( have_rows('preis_staffeln') ) : the_row();
$pos = get_row_index();
$tarif_name = get_sub_field('tarif_name');
$preis_wert = get_sub_field('preis_wert');
$ist_bestseller = (bool) get_sub_field('als_empfehlung_markieren');
$merkmale = get_sub_field('merkmale_liste');
$klassen = [ 'preis-karte', 'pos-' . $pos ];
if ( $ist_bestseller ) {
$klassen[] = 'karte-hervorgehoben';
}
if ( $pos === 1 ) {
$klassen[] = 'karte-basis';
}
if ( $pos === $anzahl ) {
$klassen[] = 'karte-enterprise';
}
$klassen[] = ( $pos % 2 === 0 ) ? 'schattierung-gerade' : 'schattierung-ungerade';
?>
<div class="<?php echo esc_attr( implode( ' ', $klassen ) ); ?>">
<?php if ( $ist_bestseller ) : ?>
<span class="badge-empfehlung"><?php esc_html_e('Beliebteste Wahl', 'wppoland'); ?></span>
<?php endif; ?>
<h3 class="tarif-titel"><?php echo esc_html( $tarif_name ); ?></h3>
<div class="preis-anzeige"><?php echo esc_html( $preis_wert ); ?></div>
<?php if ( ! empty( $merkmale ) && is_array( $merkmale ) ) : ?>
<ul class="merkmale-auflistung">
<?php foreach ( $merkmale as $punkt ) : ?>
<li><?php echo esc_html( $punkt['merkmal_text'] ?? '' ); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div class="karte-aktion">
<a href="#anfrage" class="button-buchen"><?php esc_html_e('Jetzt starten', 'wppoland'); ?></a>
</div>
</div>
<?php endwhile; ?>
</div>
</section>
<?php endif; ?>Häufig gestellte Fragen zu ACF Repeater Styling
Wie unterscheidet sich get_row_index von einem manuellen Zähler
Beginnt get_row_index bei 0 oder 1
Wann sollte Styling in CSS statt PHP erfolgen
Warum verlangsamt ein großes Repeater-Feld die Datenbank
Typensichere Datenverarbeitung und Sanitization in der Theme-Ausgabe
Beim Rendern von Benutzereingaben aus Repeater-Feldern muss die Ausgabesicherheit an oberster Stelle stehen. Selbst in geschlossenen Redaktionsumgebungen schützt strikte Ausgabebereinigung vor versehentlichen Formatierungsfehlern und Cross-Site-Scripting (XSS).
- Einfache Textfelder: Nutzen Sie
esc_html()für Überschriften, Zähler und Beschriftungen. - HTML-Attribute: Attributwerte wie Klassen, IDs oder Linkziele müssen durch
esc_attr()undesc_url()abgesichert werden. - Formatierter Text: Textbereiche mit WYSIWYG-Editor sollten stets mit
wp_kses_post()ausgegeben werden, um bösartige Skripte herauszufiltern und gleichzeitig erlaubte HTML-Tags wie Absätze und Listen beizubehalten. - Bilder und Medienanhänge: Konfigurieren Sie Unterfelder vom Typ Bild im ACF-Backend immer so, dass die Bild-ID zurückgegeben wird. Das Rendern über
wp_get_attachment_image()generiert automatisch modernesrcset-Attribute und optimierte Ladezeiten.
$kontakt_link = get_sub_field('button_url');
$link_text = get_sub_field('button_text');
if ( ! empty( $kontakt_link ) ) : ?>
<a href="<?php echo esc_url( $kontakt_link ); ?>" class="btn-interaktion" target="_blank" rel="noopener noreferrer">
<?php echo esc_html( $link_text ?: __( 'Mehr erfahren', 'wppoland' ) ); ?>
</a>
<?php endif; ?>CSS-Grid Architekturen für flexible Repeater-Karten
Um im Screendesign auf verschiedene Bildschirmgrößen flexibel zu reagieren, empfiehlt sich modernes CSS Grid in Kombination mit minmax() und auto-fit. Dadurch passen sich die Karten ohne starre Breakpoint-Deflation automatisch an:
.preis-raster {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
align-items: stretch;
}
.preis-karte {
border-radius: 12px;
padding: 2rem;
background: #ffffff;
border: 1px solid #e2e8f0;
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.preis-karte:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px -8px rgba(15, 23, 42, 0.12);
}
.preis-karte.karte-hervorgehoben {
border: 2px solid #2563eb;
position: relative;
box-shadow: 0 20px 25px -5px rgba(37, 99, 235, 0.1);
}
.preis-karte.schattierung-gerade {
background-color: #f8fafc;
}Durch diesen Aufbau bleibt das Layout auf Desktop-Monitoren dreispaltig, bricht auf Tablets automatisch auf zwei Spalten um und wird auf Smartphones einspaltig dargestellt, ohne dass Anpassungen am PHP-Code erforderlich sind.
Performance-Messung und Profiling von Datenbankabfragen in wp_postmeta
Um die tatsächliche Belastung durch umfangreiche Repeater-Felder auf Serverebene objektiv zu bewerten, sollten Entwickler strukturierte Profiling-Tools wie das Plugin Query Monitor oder PHP-Profiling mit Xdebug einsetzen.
Beim Laden einer WordPress-Seite ohne Caching führt WordPress standardmäßig einen Aufruf an wp_load_alloptions() und separate Metadatenabfragen aus. Wenn eine Beitragsseite aufgerufen wird, liest die Funktion update_postmeta_cache() alle Metadaten des aktuellen Beitrags in den internen WordPress-Objekt-Cache ein.
Warum entstehen dennoch Performance-Engpässe bei tiefen Repeatern
- Massive Serialisierung und Speicherverbrauch: Obwohl WordPress die Metadaten in einer einzigen Datenbankabfrage liest, muss ACF für jedes einzelne Unterfeld interne Filter und Feldobjekte instanziieren (
acf_get_field()). Bei 100 Unterfeldern bedeutet dies 100 interne Lookups, die PHP-Ausführungszeit und RAM beanspruchen. - Begrenzungen des MySQL-Buffers: Auf Shared-Hosting-Servern oder knapp bemessenen VPS-Instanzen können große Metadaten-Arrays den PHP-Speicher (
memory_limit) erschöpfen, insbesondere wenn Bilder und Taxonomie-Beziehungen in jedem Unterfeld verschachtelt sind. - Objekt-Cache-Serialisierung in Redis: Wenn Redis als persistenter Objekt-Cache genutzt wird, führt das Speichern und Laden von 500 KB großen serialisierten Arrays pro Beitragsaufruf zu Netzwerk-Overhead zwischen Webserver und Cache-Daemon.
Durch den gezielten Einsatz von Fragment-Caching (Transients) oder die Restrukturierung in ACF Gutenberg-Blöcke wird dieser Overhead vermieden, da nur die tatsächlich im Viewport benötigten HTML-Fragmente gerendert werden.
Fazit und Best Practices Checkliste
Das ACF Repeater-Feld bietet Redakteuren Flexibilität und Entwicklern eine saubere Datenstruktur. Durch das Zusammenspiel von get_row_index(), Modulo-Logik und modernen CSS-Pseudoklassen :nth-child() lassen sich anspruchsvolle Layouts sauber und wartungsfreundlich umsetzen.
Beachten Sie bei Ihren Projekten folgende Leitlinien:
- Schleifen sauber absichern: Immer mit
have_rows()prüfen undthe_row()korrekt aufrufen. - Unterfelder isolieren: Innerhalb der Schleife ausschließlich
get_sub_field()nutzen. - Präsentationslogik trennen: Reine Optik in CSS belassen, strukturelle Unterschiede in PHP steuern.
- Datenbank schonen: Bei großen Datenmengen Transients-Caching implementieren oder zu Gutenberg-Blöcken mit
InnerBlocksmigrieren.
Möchten Sie komplexe WordPress-Architekturen optimieren oder maßgeschneiderte Gutenberg-Themes entwickeln lassen? Profitieren Sie von unserer Expertise im Bereich WordPress-Entwicklung und technischer WordPress-Wartung.







