Shopware 6 Free Snippet · ALT · TITLE · Produktbilder · PDP · SEO · Accessibility
Shopware 6 Snippet: ALT- & TITLE-Tags für Produktbilder automatisch generieren
Dieses kostenlose Shopware-6-Snippet setzt auf Produktdetailseiten automatisch sinnvolle
ALT- und TITLE-Attribute für Produktbilder in der Galerie. Es berücksichtigt
Varianten wie Farbe, Nikotinstärke oder Ohm, überschreibt keine gepflegten Bildtexte und kann das erste
Hauptbild zusätzlich als wichtigen LCP-Kandidaten markieren.
Das Snippet arbeitet gezielt innerhalb der Produktgalerie auf der Produktdetailseite. Es erstellt aus
Produktname, Varianteninformationen und Bildposition natürliche ALT- und TITLE-Texte.
Free
ALT-Attribute automatisch setzenLeere oder generische ALT-Texte werden durch verständliche Bildbeschreibungen ersetzt.
TITLE-Attribute ergänzenFehlende oder generische TITLE-Werte werden passend zum Produkt und zur Variante gesetzt.
Varianten berücksichtigenFarbe, Nikotinstärke und Widerstand/Ohm können in die Bildtexte übernommen werden.
LCP-Bild priorisierenDas erste Hauptbild erhält loading="eager" und fetchpriority="high".
Wichtig: Das Snippet ist defensiv aufgebaut. Es ersetzt ALT/TITLE nur, wenn vorhandene Werte leer
oder generisch sind, zum Beispiel „image“, „placeholder“ oder „Produktbild“. Bereits sinnvoll gepflegte Werte bleiben erhalten.
Warum sind ALT- und TITLE-Tags bei Produktbildern wichtig?
Gute Bildattribute sind kein Ranking-Wunder, aber eine saubere technische Grundlage für Barrierefreiheit,
Bildverständnis, interne Qualität und Produktdatenkonsistenz.
Bild-SEO: Suchmaschinen erhalten klarere Bildsignale ohne Keyword-Spam.
Produktlogik: Variantenbilder werden verständlicher, wenn Farbe, Ohm oder Stärke sauber auftauchen.
Audit-Fähigkeit: Einheitliche Bildtexte helfen bei technischen SEO-Checks und Bilddatenqualität.
Performance: Das erste Hauptbild kann als LCP-relevantes Bild priorisiert werden.
Realistische Einordnung: ALT/TITLE verbessern nicht automatisch Rankings. Sie sind aber Teil einer
sauberen technischen Basis und vermeiden generische oder leere Bildinformationen.
Beispiel für die Ausgabe
Das Snippet erzeugt kurze, natürliche Texte. Die Ansicht-Zählung wird nur bei Hauptbildern genutzt.
Output
ALTProduktname – Schwarz – 0.8 Ohm – Ansicht 1/4
TITLEProduktname – Schwarz – 0.8 Ohm
LCP erstes Bildloading="eager" und fetchpriority="high"
Weitere Bilderloading="lazy" und decoding="async"
Nicht übertreiben: Bildtexte sollten beschreibend und kurz bleiben. Zu viele Variantenattribute machen
ALT/TITLE schnell unruhig und weniger hilfreich.
Einbau in Shopware 6
Füge das Skript als Custom-JavaScript ein, idealerweise am Ende der Seite beziehungsweise im Footer-Bereich,
damit die Produktgalerie bereits im DOM vorhanden ist.
Einbau
Einbauort: Theme-/Plugin-Customizing-Feld für Footer-JavaScript oder eigenes kleines Customizing-Plugin.
Scope: Das Snippet prüft gezielt auf Produktdetailseiten-Galerien und greift nicht global auf alle Bilder zu.
Testing: Produktseite öffnen, Bild inspizieren, Varianten wechseln und Hauptbild/LCP prüfen.
Theme-Abweichung: Wenn dein Theme andere Klassen nutzt, müssen Galerie- und Varianten-Selektoren angepasst werden.
Wichtig: Bitte vor Live-Einsatz testen und idealerweise ein Backup oder Rückbauplan bereithalten.
Free Snippets sind technische Beispiele und keine universelle Garantie für jedes Theme.
Code: Shopware 6 ALT-/TITLE-Automatik für PDP-Galerie
Der Code ist bewusst defensiv geschrieben: Galerie-Scoped, generische Werte werden ersetzt, gepflegte Werte bleiben erhalten.
JS
shopware6-alt-title-auto-pdp-gallery.js
<script>
(function(){
'use strict';
const CONFIG = {
includeBrand: false,
numberViews: true,
debug: false
};
const $ = (selector, root = document) => root.querySelector(selector);
const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));
const clean = value => (value || '').replace(/\s+/g, ' ').trim();
const log = (...args) => CONFIG.debug && console.log('[BORBAN-ALT-TITLE]', ...args);
function stripBrackets(value){
return clean(value)
.replace(/([^)]*)/g, ' ')
.replace(/[[^]]*]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function isGeneric(value){
value = clean(value);
return !value || /^(bild|image|produktbild|product image|thumbnail|platzhalter|placeholder|no image|picture)$/i.test(value);
}
function getProductName(){
const fromH1 = clean($('h1.product-detail-name, .product-detail-name, h1')?.textContent);
const fromTitle = clean((document.title || '').split('|')[0]);
return stripBrackets(fromH1 || fromTitle);
}
function getBrand(){
const fromText = clean($('.product-detail-manufacturer a, .manufacturer a')?.textContent);
const fromImage = clean($('.product-detail-manufacturer img[alt], .manufacturer img[alt]')?.getAttribute('alt'));
return stripBrackets(fromText || fromImage);
}
function normalizeOption(value){
value = stripBrackets(value);
if (!value) return '';
value = value
.replace(/^(farbe|color|colour|couleur|colore|kleur)\s*[:\-]\s*/i, '')
.split(/[\u2013\u2014–—-]|•/)[0]
.trim();
if (/(nicht\s*verfügbar|zur\s*zeit.*nicht|out\s*of\s*stock|unavailab|ausverkauft|currently\s*unavailable)/i.test(value)) {
return '';
}
if (value.length > 50) return '';
return value;
}
function findGroupValueByLabel(labelRegex){
const labels = Array.from(document.querySelectorAll(
'label.product-detail-configurator-group-title, .product-detail-configurator-group-title, .sw-product-variant__label, .product-variant-group-label, .product-variant-group__label'
)).filter(label => labelRegex.test(label.textContent || ''));
for (const label of labels) {
const forId = label.getAttribute('for');
if (forId) {
const target = document.getElementById(forId);
if (target && target.tagName === 'SELECT') {
const option = target.options[target.selectedIndex];
const text = normalizeOption(option?.text);
if (text) return text;
}
}
const group = label.closest('.product-detail-configurator-group, .sw-product-variants, .product-variants') || label.parentElement;
const container =
group?.querySelector('.product-detail-configurator-options, .sw-product-variants__options, .product-variant-group-options') ||
label.nextElementSibling ||
group;
const checked = container?.querySelector?.('input[type="radio"]:checked');
if (checked) {
const optionLabel = document.querySelector('label[for="' + checked.id + '"]');
const text = normalizeOption(optionLabel?.getAttribute('title') || optionLabel?.textContent || checked.value);
if (text) return text;
}
const active = container?.querySelector?.('.is-active, .is-selected, .selected, .is--active, [aria-pressed="true"], [aria-checked="true"], [aria-selected="true"]');
if (active) {
const raw = active.getAttribute('aria-label') || active.getAttribute('title') || active.getAttribute('data-original-title') || active.textContent;
const text = normalizeOption(raw);
if (text) return text;
}
const chip = container?.querySelector?.('[title], [aria-label], [data-original-title]');
if (chip) {
const text = normalizeOption(chip.getAttribute('aria-label') || chip.getAttribute('title') || chip.getAttribute('data-original-title'));
if (text) return text;
}
}
return '';
}
function findSpecValueByLabel(labelRegex){
const blocks = document.querySelectorAll('.product-detail-properties, .product-specs, dl, table');
for (const block of blocks) {
const label = Array.from(block.querySelectorAll('dt, th, .label, .name'))
.find(node => labelRegex.test(node.textContent || ''));
if (label) {
const raw = clean(label.nextElementSibling?.textContent || label.parentElement?.querySelector('dd, td, .value')?.textContent);
const text = normalizeOption(raw);
if (text) return text;
}
}
return '';
}
function getColor(){
return findGroupValueByLabel(/(farbe|color|colour)/i) ||
findSpecValueByLabel(/(farbe|color|colour)/i) ||
'';
}
function normalizeNicotine(value){
value = normalizeOption(value);
if (!value) return '';
const exact = value.match(/(\d+(?:[.,]\d+)?)\s*(mg\/?ml|mg)\b/i);
if (exact) {
const number = exact[1].replace(',', '.');
const unit = exact[2].toLowerCase().includes('ml') ? 'mg/ml' : 'mg';
return number + ' ' + unit;
}
const number = value.match(/(\d+(?:[.,]\d+)?)\b/);
if (number && /(nikotin|nic|salt|salz|strength|stärke)/i.test(value)) {
return number[1].replace(',', '.') + ' mg/ml';
}
return '';
}
function getNicotine(){
return normalizeNicotine(findGroupValueByLabel(/(nikotin|nicotine|nikotinstärke|stärke|strength)/i)) ||
normalizeNicotine(findSpecValueByLabel(/(nikotin|nicotine|nikotinstärke|stärke|strength)/i)) ||
'';
}
function normalizeResistance(value){
value = normalizeOption(value);
if (!value) return '';
const exact = value.match(/(\d+(?:[.,]\d+)?)\s*(?:ohm|Ω)\b/i);
if (exact) {
return exact[1].replace(',', '.') + ' Ohm';
}
const number = value.match(/(\d+(?:[.,]\d+)?)/);
if (number && /(widerstand|resistance|ohm|Ω)/i.test(value)) {
return number[1].replace(',', '.') + ' Ohm';
}
return '';
}
function getResistance(){
return normalizeResistance(findGroupValueByLabel(/(ohm|Ω|widerstand|resistance)/i)) ||
normalizeResistance(findSpecValueByLabel(/(ohm|Ω|widerstand|resistance)/i)) ||
'';
}
function normalizeSrc(src){
if (!src) return '';
try {
const url = new URL(src, location.origin);
let path = url.pathname;
path = path.replace(/\/thumbnail\/[^/]+\/[^/]+\/?/g, '/');
path = path.replace(/-\d+x\d+(?=\.[a-z]+$)/i, '');
return path.toLowerCase();
} catch (e) {
return String(src).toLowerCase();
}
}
function isThumbOrDecorative(img){
const closest = selector => img.closest(selector);
if (closest('.gallery-slider-thumbnails, .product-detail-thumbnails, .sw-image-slider__thumbnails, .thumbnails, .thumbnail, .is-nav, .navigation')) {
return true;
}
if (/\bthumb/i.test(img.className || '')) {
return true;
}
const width = img.naturalWidth || img.width || 0;
const height = img.naturalHeight || img.height || 0;
if (width && height && (width < 64 || height < 64)) {
return true;
}
return false;
}
function collectMainImages(wrapper){
const images = $$('img', wrapper).filter(img => img.getAttribute('src') || img.getAttribute('data-src'));
const unique = new Map();
images.forEach(img => {
if (isThumbOrDecorative(img)) return;
const key = normalizeSrc(
img.getAttribute('data-zoom-image') ||
img.getAttribute('data-src') ||
img.getAttribute('src')
);
if (!key) return;
if (!unique.has(key)) unique.set(key, img);
});
return Array.from(unique.values());
}
function buildText(options){
const parts = [];
if (options.name) parts.push(options.name);
if (
CONFIG.includeBrand &&
options.brand &&
options.name &&
!options.name.toLowerCase().includes(options.brand.toLowerCase())
) {
parts.push(options.brand);
}
if (options.color) parts.push(options.color);
if (options.resistance) parts.push(options.resistance);
if (options.nicotine) parts.push(options.nicotine);
if (CONFIG.numberViews && options.index != null && options.total) {
parts.push('Ansicht ' + options.index + '/' + options.total);
}
return parts.join(' – ');
}
function applyToGallery(){
const productName = getProductName();
if (!productName) return;
const wrapper = document.querySelector(
'.product-detail-media .gallery-slider, .product-detail-media, .product-detail-gallery'
);
if (!wrapper) return;
const brand = getBrand();
const color = getColor();
const nicotine = getNicotine();
const resistance = getResistance();
const mainImages = collectMainImages(wrapper);
if (!mainImages.length) return;
const total = mainImages.length;
const indexBySrc = new Map();
mainImages.forEach((img, index) => {
const key = normalizeSrc(
img.getAttribute('data-zoom-image') ||
img.getAttribute('data-src') ||
img.getAttribute('src')
);
if (key) indexBySrc.set(key, index + 1);
});
const allImages = $$('img', wrapper).filter(img => img.getAttribute('src') || img.getAttribute('data-src'));
allImages.forEach(img => {
if (isThumbOrDecorative(img)) return;
const key = normalizeSrc(
img.getAttribute('data-zoom-image') ||
img.getAttribute('data-src') ||
img.getAttribute('src')
);
const isMain = key && indexBySrc.has(key);
const index = isMain ? indexBySrc.get(key) : null;
const altText = buildText({
name: productName,
brand,
color,
nicotine,
resistance,
index,
total
});
const titleText = buildText({
name: productName,
brand: '',
color,
nicotine,
resistance,
index: null,
total: null
});
if (isGeneric(img.getAttribute('alt'))) {
img.setAttribute('alt', altText);
}
if (isGeneric(img.getAttribute('title'))) {
img.setAttribute('title', titleText);
}
if (isMain && index === 1) {
img.setAttribute('loading', 'eager');
img.setAttribute('fetchpriority', 'high');
} else if (!img.hasAttribute('loading')) {
img.setAttribute('loading', 'lazy');
}
if (!img.hasAttribute('decoding')) {
img.setAttribute('decoding', 'async');
}
});
const ogText = buildText({
name: productName,
brand,
color,
nicotine,
resistance,
index: null,
total: null
});
let ogAlt = document.querySelector('meta[property="og:image:alt"]');
if (!ogAlt) {
ogAlt = document.createElement('meta');
ogAlt.setAttribute('property', 'og:image:alt');
document.head.appendChild(ogAlt);
}
ogAlt.setAttribute('content', ogText);
log('ALT/TITLE updated');
}
function observeChanges(){
const media = document.querySelector(
'.product-detail-media .gallery-slider, .product-detail-media, .product-detail-gallery'
);
if (media) {
new MutationObserver(mutations => {
const hasNewImages = mutations.some(mutation =>
Array.from(mutation.addedNodes).some(node =>
node.tagName === 'IMG' || node.querySelector?.('img')
)
);
if (hasNewImages) applyToGallery();
}).observe(media, { childList: true, subtree: true });
}
const variants = document.querySelector('.product-detail-configurator, .sw-product-variants, .product-variants');
if (variants) {
new MutationObserver(() => applyToGallery()).observe(variants, {
attributes: true,
childList: true,
subtree: true
});
variants.addEventListener('change', applyToGallery, { passive: true });
variants.addEventListener('click', applyToGallery, { passive: true });
}
}
document.addEventListener('DOMContentLoaded', function(){
applyToGallery();
observeChanges();
});
})();
Konfiguration: Über includeBrand kann die Marke in ALT-Texte aufgenommen werden.
Über numberViews kann die Ansicht-Zählung für Hauptbilder aktiviert oder deaktiviert werden.
Anpassen, wenn dein Theme abweicht
Shopware-6-Themes können Galerie, Varianten und Produktdaten unterschiedlich ausgeben. Deshalb sind Selektoren
der wichtigste Anpassungspunkt.
Theme
Galerie-Wrapper: Standardmäßig sucht das Snippet in .product-detail-media, .gallery-slider und .product-detail-gallery.
Varianten-Wrapper: Beobachtet werden .product-detail-configurator, .sw-product-variants und .product-variants.
Attribute: Farbe, Nikotin und Ohm werden aus Konfiguratorgruppen oder Produkteigenschaften gelesen.
Reihenfolge: Standard ist Produktname – Farbe – Ohm – Nikotin – Ansicht.
Tipp: Nimm nur die wirklich relevanten Varianten in ALT/TITLE auf. Zu lange Bildtexte sind für Nutzer,
Screenreader und Suchmaschinen weniger hilfreich.
Free Snippet oder individuelle Lösung?
Das Snippet ist bewusst kostenlos und direkt nutzbar. Für spezielle Themes, eigene Galerie-Komponenten oder
komplexe Anforderungen kann eine individuelle Umsetzung sinnvoller sein.
Prüfe das Snippet nicht nur auf einer Produktseite. Entscheidend sind Varianten, Bilderanzahl, mobile Darstellung
und abweichende Produktdaten.
QA
Produkt mit mehreren BildernPrüfen, ob Hauptbilder sinnvolle ALT- und TITLE-Werte erhalten.
Varianten wechselnFarbe, Nikotin oder Ohm sollten korrekt in die Bildtexte übernommen werden.
Thumbnails prüfenKleine Navigationsbilder sollten nicht unnötig verändert werden.
LCP-Hinweis prüfenDas erste Hauptbild sollte fetchpriority="high" und loading="eager" erhalten.
Mobil testenGalerie und Varianten funktionieren mobil oft anders als auf Desktop.
Keine Keyword-ÜberladungBildtexte sollten natürlich, kurz und beschreibend bleiben.
Praxis-Hinweis: Wenn ein Theme stark vom Standard abweicht, ist ein kleiner individueller Check
meist schneller und sicherer als langes Herumprobieren im Live-Shop.
FAQ zum Shopware 6 ALT-/TITLE-Snippet
Kurze Antworten zu Einsatz, SEO, Accessibility, Varianten, Themes und Support.
6 Fragen
Ist das Snippet kostenlos?
Ja. Das Snippet wird kostenlos bereitgestellt. Es enthält aber keinen individuellen Einbau-, Anpassungs- oder Theme-Support.
Überschreibt das Snippet gepflegte ALT-Texte?
Nein. Das Snippet ersetzt nur leere oder generische Werte. Sinnvoll gepflegte ALT- und TITLE-Texte bleiben erhalten.
Funktioniert das Snippet mit jedem Shopware-6-Theme?
Nicht garantiert. Das Snippet nutzt DOM-Selektoren für Galerie und Varianten. Bei stark angepassten Themes müssen diese Selektoren eventuell angepasst werden.
Verbessert das Snippet automatisch mein Google-Ranking?
Nein. ALT- und TITLE-Attribute sind ein Baustein sauberer Bild-SEO und Accessibility. Konkrete Rankings lassen sich dadurch nicht garantieren.
Warum wird das erste Hauptbild priorisiert?
Das erste Hauptbild ist häufig ein wichtiger LCP-Kandidat. Deshalb setzt das Snippet dort loading="eager" und fetchpriority="high".
Wann ist ein individuelles Modul besser?
Ein individuelles Modul ist sinnvoll, wenn dein Theme stark angepasst ist, eigene Galerie-Logik nutzt oder zusätzliche Varianten- und Datenregeln benötigt werden.
Transparenz-Hinweis: Dieses Free Snippet ist ein technisches Beispiel für Shopware 6 und muss vor dem Live-Einsatz
im jeweiligen Theme geprüft werden. Produktdaten, Variantenlogik, Theme-Struktur und Galerie-Ausgabe können je nach Shop abweichen.
Digitaler Lösungsberater Hilft dir bei Plugins, Projekten und Produktenpowered by Borban
KI-gestützter Berater. Bitte keine sensiblen Daten eingeben. Produktseite und Checkout sind für Preis und Verfügbarkeit maßgeblich. Datenschutz
Diese Website verwendet Cookies, um eine bestmögliche Erfahrung bieten zu können. Mehr Informationen ...