News

Shopware Migration ohne Bauchschmerzen: mein pragmatischer Leitfaden aus Projekten

Von Erol Demirkoparan
7 min
Shopware Migration ohne Bauchschmerzen: mein pragmatischer Leitfaden aus Projekten - Cloudox Software Agentur Blog

Letzte Woche bei einem Kunden: Freitag 16:30, der alte Shop macht Zicken, und plötzlich ist klar: Wenn wir die Migration heute nicht sauber durchziehen, hängt das ganze Wochenende dran. Klingt dramatisch, war’s auch ein bisschen. Und das Verrückte: Nicht die Datenmigration war das Problem, sondern Redirects und ein winziger Unterschied in der URL-Generierung. So ein Ding, das du halt erst merkst, wenn Google & Co. schon anfangen, 404s zu sammeln.

Shopware-Migration ist selten „nur“ ein Update. Es ist eher ein Umzug mit Renovierung, bei dem du nebenbei noch die Klingelschilder (SEO) und die Lagerverwaltung (ERP) nicht verlieren darfst.

Problem Statement: Warum Shopware-Migrationen sich so oft „schmutzig“ anfühlen

In den meisten Projekten ist die Datenbankabfrage der Hauptflaschenhals

Beispiel: Enterprise E-Commerce Platform

Fehlerrate von 2.5% auf 0.3% gesenkt

Migration Checklist

  • ✓ Backup erstellt
  • ✓ Test-Umgebung aufgesetzt
  • ✓ Daten-Mapping definiert
  • ✓ Rollback-Plan dokumentiert
  • ✓ Stakeholder informiert
```mermaid graph LR A[Input] --> B[Process] B --> C[Output] ``` ```typescript // Configuration Example export const config = { environment: 'production', apiEndpoint: 'https://api.example.com', timeout: 5000, retries: 3 }; ```

Du willst eigentlich nur von A nach B: z.B. Shopware 5 auf Shopware 6, oder Shopware 6 von einer instabilen Historie auf eine saubere Neuinstallation. In der Realität hast du aber:

  • Daten, die irgendwie „gewachsen“ sind (alte Custom-Felder, Plugins, doppelte Kunden, kaputte Medienreferenzen).
  • SEO, das am seidenen Faden hängt (URLs, Canonicals, Indexierung, Weiterleitungen).
  • Integrationen, die keiner mehr anfassen will (ERP, PIM, Payment, Versand, Tracking).

Ein Fehler, den ich oft sehe: Wir migrieren „alles“, ohne vorher zu entscheiden, was eigentlich noch gebraucht wird. Das rächt sich später, weil du Müll halt perfekt konserviert hast.

Technical Background: Was bei Shopware technisch wirklich relevant ist

Wenn wir über Shopware 6 sprechen, sind ein paar Dinge entscheidend, sonst tappst du im Dunkeln:

  • DAL & IDs: Interne IDs sind UUIDs. Wenn du aus SW5 kommst, hast du andere Identitäten/Mapping-Themen.
  • SEO URLs: Shopware generiert SEO-URLs pro Sales Channel, Sprache und manchmal mit Regeln, die sich je nach Konfiguration unterscheiden.
  • Media: Medien sind nicht „nur Dateien“. Es geht um Pfade, Thumbnails, Zuordnungen, und ob du die alten Files überhaupt noch hast.
  • Plugins: Viele SW5-Plugins gibt’s in SW6 nicht 1:1. Funktionalität muss man manchmal nachbauen oder ersetzen.

Ich bin nicht 100% sicher, ob das überall gleich ist, aber in den meisten Setups scheitert’s nicht an „zu wenig Know-how“, sondern an zu wenig Klarheit: Was ist Quelle der Wahrheit? DB? PIM? ERP? Oder irgendwo ein CSV-Export von 2019.

Implementation Steps (aber locker): So gehen wir meistens ran

1) Erstmal Inventur: Daten, URLs, Integrationen

Wir ziehen uns am Anfang eine Art Snapshot:

  • Produktanzahl, Varianten, Eigenschaften, Hersteller
  • Kategorien und Tiefe
  • CMS/Shopping Experiences (falls schon SW6)
  • SEO: Top-Landingpages, Top-Kategorieseiten, Top-Produkte, wichtige Filterseiten
  • Integrationen: ERP, PIM, Payment, Versand, Tracking, Consent

Und dann kommt die Frage, die sich keiner gern stellt: Was davon ist wirklich noch relevant?

2) Migrationsweg wählen (und ehrlich sein)

Typisch:

  • SW5 → SW6: Migrations-Assistent + viel Nacharbeit (Mapping, Medien, SEO).
  • SW6 → SW6 (Rebuild): Oft die sauberste Variante, wenn das System „verpluginisiert“ ist.
  • Big Bang vs. Stufenweise: Big Bang ist schneller, stufenweise ist stressfreier – aber kostet mehr Koordination.

Übrigens: Wenn ein Kunde sagt „Wir wollen am SEO nichts verlieren“, dann ist das kein Satz. Das ist eine Anforderungsliste. 😉

3) Datenmigration: kontrolliert, wiederholbar, messbar

Wir bauen die Migration so, dass wir sie mehrfach laufen lassen können. Einmal „final“ ist nett, aber du brauchst 2–3 Probeläufe, sonst entdeckst du die echten Probleme zu spät.

-- Quick sanity checks in the target DB
-- TODO: später optimieren, aktuell reicht's für Spot-Checks
SELECT COUNT(*) AS products FROM product;
SELECT COUNT(*) AS variants FROM product WHERE parent_id IS NOT NULL;
SELECT COUNT(*) AS categories FROM category;
SELECT COUNT(*) AS customers FROM customer;

-- Find products without media assigned (often a migration pitfall)
SELECT p.product_number
FROM product p
LEFT JOIN product_media pm ON pm.product_id = p.id
WHERE pm.product_id IS NULL
LIMIT 50;

Wenn da plötzlich 30% der Produkte ohne Media sind, weißt du: Da geht’s jetzt nicht um „später mal hübsch machen“, sondern um grundlegende Referenzen.

4) SEO: Redirects, Canonicals, Indexierung – der Teil, der weh tut

Wir exportieren die wichtigsten alten URLs und mappen auf neue. Klingt banal. Ist es nicht, weil:

  • Slug-Logik ist anders
  • Kategorien können umsortiert sein
  • Mehrsprachigkeit ändert Pfade

Ein Fehler, den ich oft sehe: Man macht Redirects „ungefähr“. Also von /produktname auf /. Das ist SEO-technisch halt ein Eigentor.

import csv
import re

# Wir bauen uns hier eine simple Redirect-Mapping-Datei (alt -> neu)
# Quelle kann z.B. ein SW5 URL-Export + ein SW6 SEO-URL-Export sein.

def normalize(url: str) -> str:
    url = url.strip()
    url = re.sub(r"\?.*$", "", url)  # Query weg
    if not url.startswith("/"):
        url = "/" + url
    return url

old_to_new = {}

with open("old_urls.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        old_to_new[normalize(row["old_url"])]=None

with open("new_urls.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        # hier nehmen wir mal product_number als Join-Key, je nach Export anders
        key = row.get("product_number")
        if not key:
            continue
        new_url = normalize(row["seo_url"])  # z.B. /de/produkt/foo
        # TODO: bessere Matching-Strategie, wenn product_number nicht reicht
        old_to_new[key] = new_url

# Ausgabe als CSV für Import in Redirect-Tool oder Webserver-Regeln
with open("redirects_out.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["source", "target", "code"])
    for source, target in old_to_new.items():
        if not target:
            continue
        w.writerow([source, target, 301])

Ja, das Script ist bewusst simpel. In echten Projekten hängen wir da Matching über IDs, Produktnummern, alte SEO-Pfade, teilweise sogar Content-Hashing dran. Aber als Start ist das okay, weil du schnell siehst, wo du Lücken hast.

5) Theme/Storefront: erst Funktion, dann Pixel

Wenn du aus SW5 kommst: Vergiss, dass du das Theme „mal eben“ portierst. Wir priorisieren:

  • Checkout stabil
  • Produktdetailseite korrekt (Preis, Varianten, Verfügbarkeit)
  • Kategorie/Listing performant
  • Tracking & Consent sauber

Pixelperfekt kommt später. Sonst landest du in einem CSS-Sumpf und keiner weiß, ob der Warenkorb überhaupt tut.

// Beispiel: kleines Migrations-Utility, um im Admin-API Kontext zu prüfen,
// ob bestimmte Entities nach Migration vorhanden sind.
// TODO: später um Retry/Rate-Limit Handling erweitern

type TokenResponse = { access_token: string };

async function getToken(baseUrl: string, clientId: string, clientSecret: string) {
  const res = await fetch(`${baseUrl}/api/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "client_credentials",
      client_id: clientId,
      client_secret: clientSecret,
    }),
  });

  if (!res.ok) throw new Error(`Auth failed: ${res.status}`);
  return (await res.json()) as TokenResponse;
}

async function countProducts(baseUrl: string, token: string): Promise<number> {
  const res = await fetch(`${baseUrl}/api/search/product`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ limit: 1 }),
  });

  if (!res.ok) throw new Error(`Product search failed: ${res.status}`);
  const data = await res.json();
  return data.total as number;
}

(async () => {
  const baseUrl = process.env.SHOPWARE_URL!;
  const token = await getToken(baseUrl, process.env.CLIENT_ID!, process.env.CLIENT_SECRET!);
  const total = await countProducts(baseUrl, token.access_token);
  console.log(`Total products in target: ${total}`);
})();

Best Practices, die sich wirklich bewährt haben

  • Migration wiederholbar bauen: Wenn du nur „einmal final“ kannst, hast du eigentlich schon verloren.
  • URL-Strategie vorher festzurren: Sprache im Pfad? Kategorien im Produkt-URL? Das muss vor Redirect-Erstellung klar sein.
  • Medien früh testen: Thumbnails, CDN/Proxy, Dateirechte, Storage (S3) – das bricht gern spät und nervt.
  • Plugin-Liste radikal kürzen: Alles, was „nice to have“ ist, erstmal raus. Danach gezielt rein.
  • Observability ab Tag 1: Logs, Error-Tracking, Core Web Vitals, 404-Monitoring. Sonst tappst du nach Go-Live im Nebel.

Real-World Example: Der Redirect-Fail, der uns fast den Launch gekostet hat

Bei einem Projekt letzten Monat haben wir SW5 → SW6 migriert. Daten sahen okay aus, Checkout lief, Kunde happy. Dann kam der Pre-Launch Crawl: Plötzlich tausende 404s. Warum? Weil im alten Shop Produkt-URLs ohne Kategoriepfad waren, im neuen aber mit Kategoriepfad generiert wurden. Unser Redirect-Mapping basierte auf „Slug-only“. Hat irgendwie gepasst. Bis halt nicht mehr.

Wir haben’s dann so gefixt:

  • Top 5.000 URLs aus Analytics/SEO-Tool priorisiert
  • Mapping über Produktnummer + Sprache gebaut
  • Für den Rest: saubere Fallback-Regel auf Produktdetail via produktnummer (nicht auf Startseite)

War bisschen extra Arbeit, aber dadurch blieb das Ranking stabiler als erwartet. Und ganz ehrlich: Ohne diese Priorisierung hätten wir uns im Long Tail totoptimiert.

Wenn du mir sagst, von welchem Setup du kommst (SW5? SW6? Multishop? Sprachen? ERP/PIM?), kann ich dir auch sagen, wo ich als erstes Bauchschmerzen hätte – und was wir als „Minimalplan“ für einen sicheren Go-Live nehmen würden.

Autor

Erol Demirkoparan

Erol Demirkoparan

Senior Software Architect

Full-Stack & Cloud-Native Systems Expert. Spezialisiert auf AWS, Next.js und skalierbare SaaS-Architekturen. Building the future of automated SEO.

AWSNext.jsScalable SaaSSystem Architecture

Veröffentlicht am

6. Februar 2026

Das könnte Sie auch interessieren