Vrijdag, 16:40 Amsterdam. Een oprichter stuurt ons een mail door van haar hostingprovider. De shared box waarop haar Magento 1.9 webshop draait is gemarkeerd voor "abnormaal uitgaand verkeer" en wordt maandag om 09:00 afgesloten, tenzij de shop migreert of de installatie wordt dichtgetimmerd. De winkel doet ongeveer €1,4 miljoen per jaar aan B2B-orders en staat op pagina één voor achtendertig productcategorie-termen. Ze heeft het weekend.
We hebben dit gesprek vier of vijf keer per jaar sinds 2020. Magento 1 ging op 30 juni 2020 end-of-life. Adobe stopte die dag met security patches. Community forks (vooral OpenMage) houden de boel draaiend voor shops die de moeite namen het platform zelf te migreren, maar de meeste winkels die we erven draaien op standaard Magento 1.9.x met een toren aan third-party modules uit 2014, waarvan de helft verkocht door vendors wier Marketplace-accounts inmiddels verwijderd zijn.
Deze post is de werkelijke weekend-timeline. Wat we deden, wat we schrapten, wat we bijna misten, en het deel van de import dat de klant bijna een kwart van haar rankings kostte.
De vrijdagavond-triage
Voordat we iets aanraakten, hadden we vijf dingen nodig. Totaal aantal SKU's en categoriediepte, de live URL-inventaris (niet alleen de sitemap, de daadwerkelijke crawl), bewaartermijnen voor orderhistorie, de payment provider, en hoe customer-group pricing was bedraad in de frontend.
De crawl was het belangrijkst. We draaiden Screaming Frog van 17:00 tot 17:25. De shop had 2.143 indexeerbare URL's: 1.847 productpagina's, 142 categoriepagina's, 121 CMS-pagina's, en de rest een lange staart aan layered-navigation varianten die jaren geleden gecanoniseerd hadden moeten worden en dat nooit zijn. Die layered-navigation staart is waar de meeste weekend-migraties verkeer verliezen. Als je die URL's laat 404'en, vult Search Console zich met fouten, trekt de crawler zich terug, en worden de canonieke productpagina's twee tot drie weken minder vaak gecrawld. Dus de regel was: elke URL op die crawl krijgt een 301, ook de rotzooi, en de rotzooi gaat naar de bovenliggende categorie.
Voor de product-, klant- en orderdata exporteerden we via de Magento admin (Reports refresh, daarna catalog/product en customer/customer exports). Voor de catalogusafbeeldingen rsyncten we op de achtergrond de hele /media/catalog/product directory van de server af, terwijl we doorwerkten. Die rsync werd uiteindelijk de traagste stap van het weekend, bijna vier uur voor 38GB aan productfoto's opgebouwd sinds 2012.
Waarom Magento 1 de streep in het zand is
De discussie die we voeren met oprichters die in 2026 nog op Magento 1 draaien is altijd dezelfde. "Het werkt. We krijgen orders." Het werkt, tot de dag dat het niet meer werkt, en die dag is meestal de dag dat er een Magecart-achtige skimmer in de checkout wordt geïnjecteerd omdat een payment module uit 2017 een ongepatcht XSS-gat heeft. Adobe heeft de EOL-datum in 2020 vastgehouden en sindsdien is er niets meer uitgekomen. De PHP-versie waarop de meeste van deze shops draaien (5.6 of 7.0) is zelf ook al jaren uit support.
Het is ook de supply chain. Elke PHP-module in een typische Magento 1 installatie is een dependency die op zijn best sinds 2020 niet meer is geaudit. Sommige van die module-auteurs hebben hun accounts verwijderd; andere zijn doorverkocht. De originele Magecart-golven richtten zich specifiek op Magento checkout modules en dat risico is niet weg, het is gewoon ouder geworden. Elke module is een deur, en de mensen die die deuren bouwden zijn doorgegaan.
De 301-map is het hele werk
Als je één ding meeneemt uit deze post: de 301-redirect map is geen onderdeel van de migratie, het is de migratie. De rest is product entry. De Shopify URL-structuur ligt vast. Producten staan op /products/{handle}, collecties op /collections/{handle}, pagina's op /pages/{handle}. Magento gaf je van alles, met rewrites en trailing slashes en URL keys per store view. Je moet oud naar nieuw mappen, deterministisch, voordat je één product importeert.
We schreven een klein Node-script dat de Screaming Frog CSV plus de Magento catalog-export inlas en een Shopify-compatible redirects CSV uitspuugde. De handle-generatie logica doet ertoe: Shopify slugificeert agressief, dus een URL key als premium-leather-sofa wordt netjes /products/premium-leather-sofa, maar office-chair-(deluxe) wordt /products/office-chair-deluxe en de haakjes vallen weg. Als je de handle niet aan je eigen kant pre-computet en Shopify het bij de import laat genereren, ben je zondagmiddag met de hand het redirects-bestand aan het fiksen.
function shopifyHandle(magentoUrlKey) {
return magentoUrlKey
.toLowerCase()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '') // strip diacritics
.replace(/[^a-z0-9]+/g, '-') // non-alphanumeric to dash
.replace(/^-+|-+$/g, '') // trim leading and trailing dashes
.slice(0, 255);
}
Die functie moet exact matchen met de interne slugifier van Shopify. We testten 'm tegen een steekproef van 200 handles door een draft product via de Admin API aan te maken en de gegenereerde handle weer terug te lezen. Eenmaal geverifieerd kun je elke handle vooraf berekenen, ze in de product import CSV schrijven, en de bijbehorende 301-map in dezelfde loop wegschrijven.
Het redirects-bestand gaat Shopify in via het URL Redirects import scherm onder Online Store, Navigation. Shopify accepteert een CSV met twee kolommen: from path en to path. De harde limiet is 100.000 redirects per shop, wat veel lijkt totdat je aan layered-navigation tail URL's denkt.
Voor de SEO-theorie achter de move is Google's site move guide nog steeds de canonieke referentie en de moeite waard om de avond voor de cutover door te lezen. De korte versie: 301's (geen 302's), houd ze minstens 180 dagen live, dien de sitemap opnieuw in zodra de nieuwe URL's bereikbaar zijn.
Product import gotchas
De Shopify product CSV is mild op de makkelijke dingen (title, vendor, body HTML, image URLs) en streng op de rest. Drie valkuilen pakten ons dit weekend.
Configurable products worden varianten. Een Magento configurable product met drie kleuropties en vier maten is één Shopify product met twaalf varianten. De CSV-layout daarvoor is één rij per variant, met de parent product-velden alleen ingevuld op de eerste rij. Als je de lege parent-velden op rij twee tot en met twaalf vergeet, maakt de importer twaalf losse producten aan. We hebben dit in 2022 één keer gehad en het nooit meer vergeten.
HTML in beschrijvingen wordt gesaneerd. Magento liet je alles in een productbeschrijving stoppen, inclusief iframes, inline scripts en het soort geneste tabellayout dat in 2003 uit FrontPage rolde. Shopify strippt het meeste daarvan bij import. Als je productbeschrijvingen hebt met embedded videotutorials of tabellayouts met afmetingen, draai dan een script dat ze omzet naar schone semantische HTML vóór de import. Wij gebruiken een kleine DOMPurify-pass met een eigen allowlist.
Image URLs moeten publiek bereikbaar zijn tijdens de import. Shopify haalt de image URL op die je opgeeft, downloadt 'm, host 'm zelf, en gooit de bron weg. Als je de CSV naar de oude Magento media URL laat wijzen en de oude server staat achter een firewall of is al neergehaald, importeer je een catalogus zonder afbeeldingen. De fix is om alle afbeeldingen eerst naar een tijdelijke S3-bucket of de Shopify Files endpoint te pushen, en in de CSV naar die URL's te verwijzen.
Het B2B prijslijst-probleem
Dit is het stuk dat ons bijna brak. Magento 1 heeft customer groups standaard ingebakken: je markeert een account als "Trade", je koppelt een "Trade"-prijs aan een product, die klant ziet de trade-prijs. Shopify, op het standaard plan, heeft dit niet native. Je hebt Shopify Plus met de B2B catalogues feature nodig, of je naait het aan elkaar met een app plus metafields.
Voor deze klant was de trade-tier ongeveer 22% van de omzet en in gebruik door 340 geverifieerde accounts. Het een week verliezen was geen optie. We hebben vooraf een Shopify Plus B2B-catalogus ingericht met de trade-pricing als CSV-import, de 340 accounts gemapt als B2B-klanten met de catalogus toegewezen, en voor drie accounts een ingelogde test gedraaid vóór de cutover. De B2B-laag kostte meer tijd om in te richten dan de product-import zelf.
Als je Magento shop customer-group pricing gebruikt voor meer dan 15% van de omzet, beslis dan over je Shopify B2B-aanpak voordat je de migratie offreert. Het er achteraf inbouwen is een veel grotere klus dan het meteen goed neerzetten.
DNS, SSL en de cutover
We zetten zaterdag om 10:00 de DNS TTL op de apex en www records terug naar 300 seconden. Dat gaf ons tot zondagochtend om de wijziging door de lang-gecachte resolvers te laten propageren. De daadwerkelijke cutover was zondag 14:00 Amsterdam. We richtten de records op Shopify, wachtten 90 seconden, en draaiden de post-cutover checklist.
De checklist is kort en bot. Tik de homepage aan. Tik tien willekeurige product-URL's uit de oude crawl aan en check of elke correct 301't. Plaats een echte order met een echte kaart. Trigger een password reset en check of de mail vanuit de juiste sender binnenkomt. Dien de nieuwe sitemap in bij Search Console. Verifieer dat de robots.txt niets blokkeert (Shopify's default is verstandig, maar als je custom regels had in de oude robots.txt, kopieer ze dan in de robots.txt.liquid van het theme).
De Shopify SSL stond binnen zeven minuten na DNS-propagatie live. Dat onderdeel is het soepelste stuk van het platform.
De cijfers op maandagochtend
Maandag om 08:30 stond de shop live, was de sitemap ingediend, en zag Search Console de nieuwe URL's ontdekt worden. Over de volgende tien dagen daalde het organische verkeer ongeveer 12% week-over-week (het meeste daarvan was de layered-navigation tail die uit de index viel, wat we ook wilden), en herstelde naar de baseline op dag 11. De 38 ranking head terms bleven op positie staan. We zakten op geen enkele onder positie 4.
Twee zachte verliezen die het noemen waard zijn. De product review-teller resette van "412 reviews" naar "0 reviews" omdat de oude shop een self-hosted Magento module gebruikte die we niet schoon konden exporteren. We hebben de historische reviews een week later via Judge.me opnieuw geïmporteerd. En één productlijn kreeg zijn meta descriptions overschreven door het default Shopify-template omdat we de meta_description kolom waren vergeten te mappen in de import CSV. Dat was in twintig minuten met een bulk edit gefixt.
Een Magento 1 naar Shopify port in een weekend kan. De 301-map is het hele werk, de B2B-pricing laag is de verborgen valkuil, en je behoudt je rankings door elke URL in de oude crawl als dragend te behandelen.
Wat we anders zouden doen
Als we dit weekend opnieuw konden draaien, zouden we de rsync van de catalogusafbeeldingen naar donderdagavond verschuiven, niet vrijdagavond. Vier uur bandbreedte op het kritieke pad was het grootste planningsrisico dat we namen. We zouden ook het B2B-catalogus mapping-script (dat de 340 klanten aan de trade-catalogus koppelde) vóór vrijdag schrijven, niet zaterdagmiddag. De rest liep redelijk op tempo.
Toen we deze legacy migratie voor de B2B-distributeur draaiden, was wat we bijna misten de layered-navigation tail in de redirect-map. We losten het op door elke URL in de Screaming Frog crawl als dragend te behandelen tot het tegendeel bewezen was. Als je nu naar een Magento 1 admin paneel en een hosting-deadline zit te staren, is het kleinste nuttige dat je vandaag kunt doen een crawl draaien van je huidige shop en de URL's tellen. Dat getal is de grootte van je migratie, en het is bijna altijd groter dan de oprichter denkt.




